Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
Definition def = reachingDef.getDef(name, cfgNode);
// TODO(nicksantos): We need to add some notion of @const outer
// scope vars. We can inline those just fine.
if (def != null &&
!reachingDef.dependsOnOuterScopeVars(def)) {
candidates.add(new Candidate(name, def, n, cfgNode));
}
}
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Models the connection between a definition and a use of that definition.
*/
private class Candidate {
// Name of the variable.
private final String varName;
// Nodes related to the definition.
private Node def;
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the current CFG node.
private int numUsesWithinCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
private boolean canInline(final Scope scope) {
// Cannot inline a parameter.
if (getDefCfgNode().isFunction()) {
return false;
}
// If one of our dependencies has been inlined, then our dependency
// graph is wrong. Re-computing it would take another CFG computation,
// so we just back off for now.
for (Var dependency : defMetadata.depends) {
if (inlinedNewDependencies.contains(dependency)) {
return false;
}
}
getDefinition(getDefCfgNode());
getNumUseInUseCfgNode(useCfgNode);
// Definition was not found.
if (def == null) {
return false;
}
// Check that the assignment isn't used as a R-Value.
// TODO(user): Certain cases we can still inline.
if (def.isAssign() && !NodeUtil.isExprAssign(def.getParent())) {
return false;
}
// The right of the definition has side effect:
// Example, for x:
// x = readProp(b), modifyProp(b); print(x);
if (checkRightOf(def, getDefCfgNode(), SIDE_EFFECT_PREDICATE)) {
return false;
}
// Similar check as the above but this time, all the sub-expressions
// left of the use of the variable.
// x = readProp(b); modifyProp(b), print(x);
if (checkLeftOf(use, useCfgNode, SIDE_EFFECT_PREDICATE)) {
return false;
}
// TODO(user): Side-effect is OK sometimes. As long
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> as there are no
// side-effect function down all paths to the use. Once we have all the
// side-effect analysis tool.
if (NodeUtil.mayHaveSideEffects(def.getLastChild(), compiler)) {
return false;
}
// TODO(user): We could inline all the uses if the expression is short.
// Finally we have to make sure that there are no more than one use
// in the program and in the CFG node. Even when it is semantically
// correctly inlining twice increases code size.
if (numUsesWithinCfgNode != 1) {
return false;
}
// Make sure that the name is not within a loop
if (NodeUtil.isWithinLoop(use)) {
return false;
}
Collection<Node> uses = reachingUses.getUses(varName, getDefCfgNode());
if (uses.size() != 1) {
return false;
}
// We give up inlining stuff with R-Value that has:
// 1) GETPROP, GETELEM,
// 2) anything that creates a new object.
// 3) a direct reference to a catch expression.
// Example:
// var x = a.b.c; j.c = 1; print(x);
// Inlining print(a.b.c) is not safe consider j and be alias to a.b.
// TODO(user): We could get more accuracy by looking more in-detail
// what j is and what x is trying to into to.
// TODO(johnlenz): rework catch expression handling when we
// have lexical scope support so catch expressions don't
// need to be special cased.
if (NodeUtil.has(def.getLastChild(),
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
switch (input.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.NEW:
return true;
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var != null
&& var.getParentNode().isCatch()) {
return true;
}
}
return false;
}
},
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
// Recurse if the node is not a function.
return !input.isFunction();
}
})) {
return false;
}
// We can skip the side effect check along the paths of two nodes if
// they are just next to each other.
if (NodeUtil.isStatementBlock(getDefCfgNode().getParent()) &&
getDefCfgNode().getNext() != useCfgNode) {
// Similar side effect check as above but this time the side effect is
// else where along the path.
// x = readProp(b); while(modifyProp(b)) {}; print(x);
CheckPaths
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>BetweenNodes<Node, ControlFlowGraph.Branch>
pathCheck = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>(
cfg,
cfg.getDirectedGraphNode(getDefCfgNode()),
cfg.getDirectedGraphNode(useCfgNode),
SIDE_EFFECT_PREDICATE,
Predicates.
<DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(),
false);
if (pathCheck.somePathsSatisfyPredicate()) {
return false;
}
}
return true;
}
/**
* Actual transformation.
*/
private void inlineVariable() {
Node defParent = def.getParent();
Node useParent = use.getParent();
if (def.isAssign()) {
Node rhs = def.getLastChild();
rhs.detachFromParent();
// Oh yes! I have grandparent to remove this.
Preconditions.checkState(defParent.isExprResult());
while (defParent.getParent().isLabel()) {
defParent = defParent.getParent();
}
defParent.detachFromParent();
useParent.replaceChild(use, rhs);
} else if (defParent.isVar()) {
Node rhs = def.getLastChild();
def.removeChild(rhs);
useParent.replaceChild(use, rhs);
} else {
Preconditions.checkState(false, "No other definitions can be inlined.");
}
compiler.reportCodeChange();
}
/**
* Set the def node
*
* @param n A node that has a corresponding CFG node in the CFG.
*/
private void getDefinition(Node n) {
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (n.getString().equals(varName) && n.hasChildren()) {
def = n;
}
return;
case Token.ASSIGN:
Node lhs = n.getFirstChild();
if (lhs.isName() && lhs.getString().equals(varName)) {
*/
private static boolean checkRightOf(
Node n, Node expressionRoot, Predicate<Node> predicate) {
for (Node p = n; p != expressionRoot; p = p.getParent()) {
for (Node cur = p.getNext(); cur != null; cur = cur.getNext()) {
if (predicate.apply(cur)) {
return true;
}
}
}
return false;
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the left of n.
*
* Example:
*
* Checked(), Checked(), n, NotChecked(), NotChecked();
*/
private static boolean checkLeftOf(
Node n, Node expressionRoot, Predicate<Node> predicate) {
for (Node p = n; p != expressionRoot; p = p.getParent()) {
for (Node
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> cur = p.getParent().getFirstChild(); cur != p;
cur = cur.getNext()) {
if (predicate.apply(cur)) {
return true;
}
}
}
return false;
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.<String, String>newLinkedHashMap());
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.CONSISTENT, previousMap.get(name)));
} else if (doc.isStableIdGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.STABLE, previousMap.get(name)));
} else if (doc.isIdGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.INCONSISTENT, previousMap.get(name)));
} else if (doc.isMappedIdGenerator()) {
NameSupplier supplier = nameGenerators.get(name);
if (supplier == null
|| supplier.getRenameStrategy() != RenameStrategy.MAPPED) {
compiler.report(t.makeError(n, MISSING_NAME_MAP_FOR_GENERATOR));
// skip registering the name in the list of Generators if there no
// mapping.
return;
}
} else {
throw new IllegalStateException("unexpected");
}
idGeneratorMaps.put(name, Maps.<String, String>newLinkedHashMap());
}
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, new GatherGenerators());
if (!nameGenerators.isEmpty()) {
NodeTraversal.traverse(compiler, root, new ReplaceGenerators());
}
}
private class ReplaceGenerators extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
NameSupplier nameGenerator = nameGenerators.get(callName);
if (nameGenerator == null) {
return;
}
if (!t.inGlobalScope() &&
nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) {
// Warn about calls not in the global scope.
compiler.report(t.makeError(n, NON_GLOBAL_ID_GENERATOR_CALL));
return;
}
if (nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) {
for (Node ancestor : n.getAncestors()) {
if (NodeUtil.isControlStructure(ancestor)) {
// Warn about conditional calls.
compiler.report(t.makeError(n, CONDITIONAL_ID_GENERATOR_CALL));
return;
}
}
}
Node arg = n.getFirstChild().getNext();
if (arg.isString()) {
String rename = getObfuscatedName(
arg, callName, nameGenerator, arg.getString());
parent.replaceChild(n, IR.string(rename));
compiler.reportCodeChange();
} else if (arg.isObjectLit()) {
for (Node key : arg.children()) {
String rename = getObfuscatedName(
key, callName, nameGenerator, key.getString());
key.setString(rename);
// Prevent standard renaming by marking the key as quoted
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> the line length and handling
* indenting for pretty printing.
*/
@Override
void startNewLine() {
if (lineLength > 0) {
code.append('\n');
lineIndex++;
lineLength = 0;
}
}
@Override
void maybeLineBreak() {
maybeCutLine();
}
/**
* This may start a new line if the current line is longer than the line
* length threshold.
*/
@Override
void maybeCutLine() {
if (lineLength > lineLengthThreshold) {
startNewLine();
}
}
@Override
void endLine() {
startNewLine();
}
@Override
void appendBlockStart() {
append(" {");
indent++;
}
@Override
void appendBlockEnd() {
endLine();
indent--;
append("}");
}
@Override
void listSeparator() {
add(", ");
maybeLineBreak();
}
@Override
void endFunction(boolean statementContext) {
super.endFunction(statementContext);
if (statementContext) {
startNewLine();
}
}
@Override
void beginCaseBody() {
super.beginCaseBody();
indent++;
endLine();
}
@Override
void endCaseBody() {
super.endCaseBody();
indent--;
endStatement();
}
@Override
void appendOp(String op, boolean binOp) {
if (binOp) {
if (getLastChar() != ' ' && op.charAt(0) != ',') {
append(" ");
}
append(op);
append(" ");
} else {
append(op);
}
}
/**
* If the body of a for loop or the then clause of an if statement has
* a single statement, should it be wrapped in a block?
* {@inheritDoc}
*/
@Override
boolean shouldPreserveExtraBlocks() {
// When pretty-printing, always place the statement in its own block
// so it is printed on a separate line. This allows breakpoints to be
// placed on the statement.
return true;
}
/**
* @return The TRY node for the specified CATCH node.
*/
private Node getTryForCatch(Node n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatementContext) {
Preconditions.checkState(n.isBlock());
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
//
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null) {
return;
}
// Do not try to remove a block or an expr result. We already handle
// these cases when we visit the child, and the peephole passes will
// fix up the tree in more clever ways when these are removed.
if (n.isExprResult() || n.isBlock()) {
return;
}
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
return;
}
boolean isResultUsed = NodeUtil.isExpressionResultUsed(n);
boolean isSimpleOp = NodeUtil.isSimpleOperator(n);
if (!isResultUsed &&
(isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) {
String msg = "This code lacks side-effects. Is there a bug?";
if (n.isString()) {
msg = "Is there a missing '+' on the previous line?";
} else if (isSimpleOp) {
msg = "The result of the '" + Token.name(n.getType()).toLowerCase() +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
// TODO(johnlenz): determine if it is necessary to
// try to protect side-effect free statements as well.
if (!NodeUtil.isStatement(n)) {
problemNodes.add(n);
}
}
}
/**
* Protect side-effect free nodes by making them parameters
* to a extern function call. This call will be removed
* after all the optimizations passes have run.
*/
private void protectSideEffects() {
if (!problemNodes.isEmpty()) {
addExtern();
for (Node n : problemNodes) {
Node name = IR.name(PROTECTOR_FN).srcref(n);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node replacement = IR.call(name).srcref(n);
replacement.putBooleanProp(Node.FREE_CALL, true);
n.getParent().replaceChild(n, replacement);
replacement.addChildToBack(n);
}
compiler.reportCodeChange();
}
}
private void addExtern() {
Node name = IR.name(PROTECTOR_FN);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean checkOnly;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean checkOnly) {
this.compiler = compiler;
this.checkOnly = checkOnly;
}
private void reportChange() {
if (checkOnly) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
if (checkOnly) {
normalizeNodeTypes(root);
} else {
// Don't perform "PrepareAnnotations" when doing checks as
// they currently aren't valid during sanity checks. In particular,
// they DIRECT_EVAL shouldn't be applied after inlining has been
// performed.
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations());
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations());
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> final
Function<TypePair, TypePair> SHEQ =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowEquality(p.typeB);
}
};
/**
* Merging function for strict non-equality between types.
*/
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final Function<TypePair, TypePair> ineq =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
public SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
@Override
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.isTypeOf() && right.isString()) {
typeOfNode = left;
stringNode = right;
} else if (right.isTypeOf() &&
left.isString()) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
}
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, ineq);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().isString()) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
// Don't traverse functions that are constructors or have the @this
// or @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING_KEY)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n) {
Node parent = n.getParent();
if (assignLhs
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>Child != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.isVar()) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
Ref decl = name.getDeclaration();
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
decl.getSourceName(),
decl.node, INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define was already initialized, and this is an unsafe
// re-assignment.
compiler.report(
t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR,
name, info.getReasonWhyNotAssignable()));
}
}
return false;
}
/**
* Gets the parent node of the value for any assignment to a Name.
* For example, in the assignment
* {@code var x = 3;}
* the parent would be the NAME node.
*/
private static Node getValueParent(Ref ref) {
// there are two types of declarations: VARs and ASSIGNs
return ref.node.getParent() != null &&
ref.node.getParent().isVar() ?
ref.node : ref.node.getParent();
}
/**
* Records the fact that because of the current node in the node traversal,
* the define can't ever be assigned again.
*
* @param info Represents the define variable.
* @param t The current traversal.
*/
private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) {
info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE,
t.getLineNumber(), t.getSourceName()));
}
/**
* A simple data structure for associating a Ref with the name
* that it references.
*/
private static class RefInfo {
final Ref ref;
final Name name;
RefInfo(Ref ref, Name name) {
this.ref = ref;
this.name = name;
}
}
}
/**
* A simple class for storing information about a define.
*
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false,
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
inputId = NodeUtil.getInputId(root);
sourceName = "";
curNode = root;
pushScope(root);
// null parent ensures that the shallow callbacks will traverse root
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
inputId = NodeUtil.getInputId(scopeRoot);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
String sourceFileName = getBestSourceFileName(n);
if (sourceFileName == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceFileName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
inputId = null;
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.isFunction()) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
}
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
public AbstractCompiler getCompiler() {
return compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >= 0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(inputId);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traversal for passes that work only on changed functions.
* Suppose a loopable pass P1 uses this traversal.
* Then, if a function doesn't change between two runs of P1, it won't look at
* the function the second time.
* (We're assuming that P1 runs to a fixpoint, o/w we may miss optimizations.)
*
* Most changes are reported with calls to Compiler.reportCodeChange(), which
* doesn't know which scope changed. We keep track of the current scope by
* calling Compiler.setScope inside pushScope and popScope.
* The automatic tracking can be wrong in rare cases when a pass changes scope
* w/out causing a call to pushScope or popScope. It
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>'s very hard to find the
* places where this happens unless a bug is triggered.
* Passes that do cross-scope modifications call
* Compiler.reportChangeToEnclosingScope(Node n).
*/
public static void traverseChangedFunctions(
AbstractCompiler compiler, FunctionCallback callback) {
final AbstractCompiler comp = compiler;
final FunctionCallback cb = callback;
final Node jsRoot = comp.getJsRoot();
NodeTraversal t = new NodeTraversal(comp, new AbstractPreOrderCallback() {
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n, Node p) {
if ((n == jsRoot || n.isFunction()) && comp.hasScopeChanged(n)) {
cb.visit(comp, n);
}
return true;
}
});
t.traverse(jsRoot);
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
public static void traverseRoots(
AbstractCompiler compiler, Callback cb, Node ... roots) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) {
return;
}
if (type == Token.FUNCTION) {
traverseFunction(n, parent);
} else {
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
}
curNode = n;
callback.visit(this, n, parent);
}
/** Traverses a function. */
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> 'var e = 1' would be rewritten as 'e = 1'.
// TODO(johnlenz): Introduce a separate scope for catch nodes.
removeDuplicateDeclarations(externs, root);
new PropagateConstantAnnotationsOverVars(compiler, assertOnChange)
.process(externs, root);
FindExposeAnnotations findExposeAnnotations = new FindExposeAnnotations();
NodeTraversal.traverse(compiler, root, findExposeAnnotations);
if (!findExposeAnnotations.exposedProperties.isEmpty()) {
NodeTraversal.traverse(compiler, root,
new RewriteExposedProperties(
findExposeAnnotations.exposedProperties));
}
if (!compiler.getLifeCycleStage().isNormalized()) {
compiler.setLifeCycleStage(LifeCycleStage.NORMALIZED);
}
}
/**
* Find all the @expose annotations.
*/
private static class FindExposeAnnotations extends AbstractPostOrderCallback {
private final Set<String> exposedProperties = Sets.newHashSet();
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isExprAssign(n)) {
Node assign = n.getFirstChild();
Node lhs = assign.getFirstChild();
if (lhs.isGetProp() && isMarkedExpose(assign)) {
exposedProperties.add(lhs.getLastChild().getString());
}
} else if (n.isStringKey() && isMarkedExpose(n)) {
exposedProperties.add(n.getString());
} else if (n.isGetProp() && n.getParent().isExprResult()
&& isMarkedExpose(n)) {
exposedProperties.add(n.getLastChild().getString());
}
}
private boolean isMarkedExpose(Node n) {
JSDocInfo info = n.getJSDocInfo();
return info != null && info.isExpose();
}
}
/**
* Rewrite all exposed properties in [] form.
*/
private class RewriteExposedProperties
extends AbstractPostOrderCallback {
private final Set<String> exposedProperties;
RewriteExposedProperties(Set<String> exposedProperties) {
this.exposedProperties = exposedProperties;
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
String propName = n.getLastChild().getString();
if (exposedProperties.contains(propName)) {
Node obj = n.removeFirstChild();
Node prop = n.removeFirstChild();
n.getParent().replaceChild(n, IR.getelem(obj, prop));
compiler.reportCodeChange();
}
} else if (n.isStringKey()) {
String propName = n.getString();
if (exposedProperties.contains(propName)) {
n.setQuotedString();
compiler.reportCodeChange();
}
}
}
}
/**
* Propagate constant annotations over the Var graph.
*/
static class PropagateConstantAnnotationsOverVars
extends AbstractPostOrderCallback
implements CompilerPass {
private
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> final AbstractCompiler compiler;
private final boolean assertOnChange;
PropagateConstantAnnotationsOverVars(
AbstractCompiler compiler, boolean forbidChanges) {
this.compiler = compiler;
this.assertOnChange = forbidChanges;
}
@Override
public void process(Node externs, Node root) {
new NodeTraversal(compiler, this).traverseRoots(externs, root);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Note: Constant properties annotations are not propagated.
if (n.isName()) {
if (n.getString().isEmpty()) {
return;
}
JSDocInfo info = null;
// Find the JSDocInfo for a top-level variable.
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
boolean shouldBeConstant =
(info != null && info.isConstant()) ||
NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), n, parent);
boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (shouldBeConstant && !isMarkedConstant) {
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name + "\n" +
" parent:" + n.getParent().toStringTree());
}
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
}
/**
* Walk the AST tree and verify that constant names are used consistently.
*/
static class VerifyConstants extends AbstractPostOrderCallback
implements CompilerPass {
final private AbstractCompiler compiler;
final private boolean checkUserDeclarations;
VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) {
this.compiler = compiler;
this.checkUserDeclarations = checkUserDeclarations;
}
@Override
public void process(Node externs, Node root) {
Node externsAndJs = root.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(externsAndJs.hasChild(externs));
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
private Map<String, Boolean> constantMap = Maps.newHashMap();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
String name = n.getString();
if (n.getString().isEmpty()) {
return;
}
boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (checkUserDeclarations) {
boolean expectedConst = false;
CodingConvention convention = compiler.getCodingConvention();
if (NodeUtil.isConstantName(n)
|| NodeUtil.isConstantByConvention(convention, n, parent)) {
expectedConst = true;
} else {
expected
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
parent.replaceChild(n, n.removeFirstChild());
break;
}
}
/**
* Mark names and properties that are constants by convention.
*/
private void annotateConstantsByConvention(Node n, Node parent) {
Preconditions.checkState(
n.isName()
|| n.isString()
|| n.isStringKey()
|| n.isGetterDef()
|| n.isSetterDef());
// There are only two cases where a string token
// may be a variable reference: The right side of a GETPROP
// or an OBJECTLIT key.
boolean isObjLitKey = NodeUtil.isObjectLitKey(n);
boolean isProperty = isObjLitKey ||
(parent.isGetProp() &&
parent.getLastChild() == n);
if (n.isName() || isProperty) {
boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (!isMarkedConstant &&
NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), n, parent)) {
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name + "\n" +
" parent:" + n.getParent().toStringTree());
}
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
/**
* Rewrite named unhoisted functions declarations to a known
* consistent behavior so we don't to different logic paths for the same
* code. From:
* function f() {}
* to:
* var f = function () {};
*/
private void normalizeFunctionDeclaration(Node n) {
Preconditions.checkState(n.isFunction());
if (!NodeUtil.isFunctionExpression(n)
&& !NodeUtil.isHoistedFunctionDeclaration(n)) {
rewriteFunctionDeclaration(n);
}
}
/**
* Rewrite the function declaration from:
* function x() {}
* FUNCTION
* NAME
* LP
* BLOCK
* to:
* var x = function() {};
* VAR
* NAME
* FUNCTION
* NAME (w/ empty string)
* LP
* BLOCK
*/
private void rewriteFunctionDeclaration(Node n) {
// Prepare a spot for the function.
Node oldNameNode = n.getFirstChild();
Node fnNameNode = oldNameNode.cloneNode();
Node var = IR.var(fnNameNode).srcref(n);
// Prepare the function
oldNameNode.setString("");
// Move the function
Node parent = n.getParent();
parent.replaceChild(n, var);
fnNameNode.addChildToFront(n);
reportCodeChange("Function declaration");
}
/**
* Do normalizations that introduce new siblings or parents.
*/
private void doStatementNormalizations(Node n) {
if (n.isLabel()) {
normalizeLabels(n);
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.getParent().replaceChild(first, name);
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR-IN var declaration");
}
} else if (!c.getFirstChild().isEmpty()) {
Node init = c.getFirstChild();
Node empty = IR.empty();
empty.copyInformationFrom(c);
c.replaceChild(init, empty);
Node newStatement;
// Only VAR statements, and expressions are allowed,
// but are handled differently.
if (init.isVar()) {
newStatement = init;
} else {
newStatement = NodeUtil.newExpr(init);
}
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR initializer");
}
break;
}
}
}
/**
* Split a var node such as:
* var a, b;
* into individual statements:
* var a;
* var b;
* @param n The whose children we should inspect.
*/
private void splitVarDeclarations(Node n) {
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
if (c.isVar()) {
if (assertOnChange && !c.hasChildren()) {
throw new IllegalStateException("Empty VAR node.");
}
while (c.getFirstChild() != c.getLastChild()) {
Node name = c.getFirstChild();
c.removeChild(name);
Node newVar = IR.var(name).srcref(n);
n.addChildBefore(newVar, c);
reportCodeChange("VAR with multiple children");
}
}
}
}
/**
* Move all the functions that are valid at the execution of the first
* statement of the function to the beginning of the function definition.
*/
private void moveNamedFunctions(Node functionBody) {
Preconditions.checkState(
functionBody.getParent().isFunction());
Node previous = null;
Node current = functionBody.getFirstChild();
// Skip any declarations at the beginning of the function body, they
// are already in the right place.
while (current != null && NodeUtil.isFunctionDeclaration(current)) {
previous = current;
current = current.getNext();
}
// Find any remaining declarations and move them.
Node insertAfter = previous;
while (current != null) {
// Save off the next node as the current node maybe removed.
Node next = current.getNext();
if (NodeUtil.isFunctionDeclaration(current)) {
// Remove the declaration from the body.
Preconditions.checkNotNull(previous);
functionBody.removeChildAfter(previous);
// Read the function at the top of the function body (after any
// previous declarations).
insertAfter = addToFront(functionBody, current, insertAfter);
reportCodeChange("Move function declaration not at top of function");
} else {
// Update the previous only if the current node hasn't been moved.
previous = current;
}
current = next;
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
}
}
/**
* @param after The child node to insert the newChild after, or null if
* newChild should be added to the front of parent's child list.
* @return The inserted child node.
*/
private Node addToFront(Node parent, Node newChild, Node after) {
if (after == null) {
parent.addChildToFront(newChild);
} else {
parent.addChildAfter(newChild, after);
}
return newChild;
}
}
/**
* Remove duplicate VAR declarations.
*/
private void removeDuplicateDeclarations(Node externs, Node root) {
Callback tickler = new ScopeTicklingCallback();
ScopeCreator scopeCreator = new SyntacticScopeCreator(
compiler, new DuplicateDeclarationHandler());
NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator);
t.traverseRoots(externs, root);
}
/**
* ScopeCreator duplicate declaration handler.
*/
private final class DuplicateDeclarationHandler implements
SyntacticScopeCreator.RedeclarationHandler {
private Set<Var> hasOkDuplicateDeclaration = Sets.newHashSet();
/**
* Remove duplicate VAR declarations encountered discovered during
* scope creation.
*/
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Var v = s.getVar(name);
if (v != null && s.isGlobal()) {
// We allow variables to be duplicate declared if one
// declaration appears in source and the other in externs.
// This deals with issues where a browser built-in is declared
// in one browser but not in another.
if (v.isExtern() && !input.isExtern()) {
if (hasOkDuplicateDeclaration.add(v)) {
return;
}
}
}
// If name is "arguments", Var maybe null.
if (v != null && v.getParentNode().isCatch()) {
// Redeclaration of a catch expression variable is hard to model
// without support for "with" expressions.
// The ECMAScript spec (section 12.14), declares that a catch
// "catch (e) {}" is handled like "with ({'e': e}) {}" so that
// "var e" would refer to the scope variable, but any following
// reference would still refer to "e" of the catch expression.
// Until we have support for this disallow it.
// Currently the Scope object adds the catch expression to the
// function scope, which is technically not true but a good
// approximation for most uses.
// TODO(johnlenz): Consider improving how scope handles catch
// expression.
// Use the name of the var before it was made unique.
name = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName(
name);
compiler.report(
JSError.make(
input.getName(), n,
CATCH
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>_BLOCK_VAR_ERROR, name));
} else if (v != null && parent.isFunction()) {
if (v.getParentNode().isVar()) {
s.undeclare(v);
s.declare(name, n, n.getJSType(), v.input);
replaceVarWithAssignment(v.getNameNode(), v.getParentNode(),
v.getParentNode().getParent());
}
} else if (parent.isVar()) {
Preconditions.checkState(parent.hasOneChild());
replaceVarWithAssignment(n, parent, parent.getParent());
}
}
/**
* Remove the parent VAR. There are three cases that need to be handled:
* 1) "var a = b;" which is replaced with "a = b"
* 2) "label:var a;" which is replaced with "label:;". Ideally, the
* label itself would be removed but that is not possible in the
* context in which "onRedeclaration" is called.
* 3) "for (var a in b) ..." which is replaced with "for (a in b)..."
* Cases we don't need to handle are VARs with multiple children,
* which have already been split into separate declarations, so there
* is no need to handle that here, and "for (var a;;);", which has
* been moved out of the loop.
* The result of this is that in each case the parent node is replaced
* which is generally dangerous in a traversal but is fine here with
* the scope creator, as the next node of interest is the parent's
* next sibling.
*/
private void replaceVarWithAssignment(Node n, Node parent, Node gramps) {
if (n.hasChildren()) {
// The * is being initialize, preserve the new value.
parent.removeChild(n);
// Convert "var name = value" to "name = value"
Node value = n.getFirstChild();
n.removeChild(value);
Node replacement = IR.assign(n, value);
replacement.copyInformationFrom(parent);
gramps.replaceChild(parent, NodeUtil.newExpr(replacement));
} else {
// It is an empty reference remove it.
if (NodeUtil.isStatementBlock(gramps)) {
gramps.removeChild(parent);
} else if (gramps.isFor()) {
// This is the "for (var a in b)..." case. We don't need to worry
// about initializers in "for (var a;;)..." as those are moved out
// as part of the other normalizations.
parent.removeChild(n);
gramps.replaceChild(parent, n);
} else {
Preconditions.checkState(gramps.isLabel());
// We should never get here. LABELs with a single VAR statement should
// already have been normalized to have a BLOCK.
throw new IllegalStateException("Unexpected LABEL");
}
}
reportCode
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>()
|| node.isFunction()
|| node.isName()
|| NodeUtil.isGet(node)
|| NodeUtil.isObjectLitKey(node)
|| (node.isString() && NodeUtil.isGet(node.getParent()));
}
};
}
public static class LocationMapping {
final String prefix;
final String replacement;
public LocationMapping(String prefix, String replacement) {
this.prefix = prefix;
this.replacement = replacement;
}
}
private final SourceMapGenerator generator;
private List<LocationMapping> prefixMappings = Collections.emptyList();
private final Map<String, String> sourceLocationFixupCache =
Maps.newHashMap();
private SourceMap(SourceMapGenerator generator) {
this.generator = generator;
}
public void addMapping(
Node node,
FilePosition outputStartPosition,
FilePosition outputEndPosition) {
String sourceFile = node.getSourceFileName();
// If the node does not have an associated source file or
// its line number is -1, then the node does not have sufficient
// information for a mapping to be useful.
if (sourceFile == null || node.getLineno() < 0) {
return;
}
sourceFile = fixupSourceLocation(sourceFile);
String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP);
// Strangely, Rhino source lines are one based but columns are
// zero based.
// We don't change this for the v1 or v2 source maps but for
// v3 we make them both 0 based.
int lineBaseOffset = 1;
if (generator instanceof SourceMapGeneratorV1
|| generator instanceof SourceMapGeneratorV2) {
lineBaseOffset = 0;
}
generator.addMapping(
sourceFile, originalName,
new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()),
outputStartPosition, outputEndPosition);
}
/**
* @param sourceFile The source file location to fixup.
* @return a remapped source file.
*/
private String fixupSourceLocation(String sourceFile) {
if (prefixMappings.isEmpty()) {
return sourceFile;
}
String fixed = sourceLocationFixupCache.get(sourceFile);
if (fixed != null) {
return fixed;
}
// Replace the first prefix found with its replacement
for (LocationMapping mapping : prefixMappings) {
if (sourceFile.startsWith(mapping.prefix)) {
fixed = mapping.replacement + sourceFile.substring(
mapping.prefix.length());
break;
}
}
// If none of the mappings match then use the original file path.
if (fixed == null) {
fixed = sourceFile;
}
sourceLocationFixupCache.put(sourceFile, fixed);
return fixed;
}
public void appendTo(Appendable out, String name) throws IOException {
generator.appendTo(out, name);
}
public void reset()
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> (maybeDecl.isVarDeclaration()) {
Preconditions.checkState(!maybeDecl.isInitializingDeclaration());
Reference maybeInit = references.get(index);
if (maybeInit.isSimpleAssignmentToName()) {
return true;
}
}
}
return false;
}
/**
* @return The reference that provides the value for the variable at the
* time of the first read, if known, otherwise null.
*
* This is either the variable declaration ("var a = ...") or first
* reference following the declaration if it is an assignment.
*/
Reference getInitializingReference() {
if (isInitializingDeclarationAt(0)) {
return references.get(0);
} else if (isInitializingAssignmentAt(1)) {
return references.get(1);
}
return null;
}
/**
* Constants are allowed to be defined after their first use.
*/
Reference getInitializingReferenceForConstants() {
int size = references.size();
for (int i = 0; i < size; i++) {
if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) {
return references.get(i);
}
}
return null;
}
/**
* @return Whether the variable is only assigned a value once for its
* lifetime.
*/
boolean isAssignedOnceInLifetime() {
Reference ref = getOneAndOnlyAssignment();
if (ref == null) {
return false;
}
// Make sure this assignment is not in a loop.
for (BasicBlock block = ref.getBasicBlock();
block != null; block = block.getParent()) {
if (block.isFunction) {
break;
} else if (block.isLoop) {
return false;
}
}
return true;
}
/**
* @return The one and only assignment. Returns if there are 0 or 2+
* assignments.
*/
private Reference getOneAndOnlyAssignment() {
Reference assignment = null;
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
if (assignment == null) {
assignment = ref;
} else {
return null;
}
}
}
return assignment;
}
/**
* @return Whether the variable is never assigned a value.
*/
boolean isNeverAssigned() {
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> * Represents a single declaration or reference to a variable.
*/
static final class Reference implements StaticReference<JSType> {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final BasicBlock basicBlock;
private final Scope scope;
private final InputId inputId;
private final StaticSourceFile sourceFile;
Reference(Node nameNode, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(),
basicBlock, t.getScope(), t.getInput().getInputId());
}
/**
* Creates a variable reference in a given script file name, used in tests.
*
* @return The created reference.
*/
@VisibleForTesting
static Reference createRefForTest(CompilerInput input) {
return new Reference(new Node(Token.NAME), null, null,
input.getInputId());
}
private Reference(Node nameNode,
BasicBlock basicBlock, Scope scope, InputId inputId) {
this.nameNode = nameNode;
this.basicBlock = basicBlock;
this.scope = scope;
this.inputId = inputId;
this.sourceFile = nameNode.getStaticSourceFile();
}
/**
* Makes a copy of the current reference using a new Scope instance.
*/
Reference cloneWithNewScope(Scope newScope) {
return new Reference(nameNode, basicBlock, newScope, inputId);
}
@Override
public Var getSymbol() {
return scope.getVar(nameNode.getString());
}
@Override
public Node getNode() {
return nameNode;
}
public InputId getInputId() {
return inputId;
}
@Override
public StaticSourceFile getSourceFile() {
return sourceFile;
}
boolean isDeclaration() {
Node parent = getParent();
Node grandparent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
!getParent().isVar() ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue(nameNode);
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.isFunction();
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is equivalent to the very first block that
* is created when reference collection traversal enters global scope. Note
* that when traversing a single script in a hot-swap fashion a new instance
* of
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> {@code BasicBlock} is created.
*
* @return true if this is global scope block.
*/
boolean isGlobalScopeBlock() {
return getParent() == null;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
if (currentBlock == this) {
return true;
}
if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) {
return true;
}
return false;
}
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
@Override
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final Scope outerScope = t.getScope();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false)
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
if (rightType.isStruct()) {
report(t, right, IN_USED_WITH_STRUCT);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.INSTANCEOF:
left = n.getFirstChild();
right = n.getLastChild();
rightType = getJSType(right).restrictByNotNullOrUndefined();
validator.expectAnyObject(
t, left, getJSType(left), "deterministic instanceof yields false");
validator.expectActualObject(
t, right, rightType, "instanceof requires an object");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.ASSIGN:
visitAssign(t, n);
typeable = false;
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
checkPropCreation(t, n.getFirstChild());
// fall through
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT_CASE:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.IF:
case Token.WHILE:
typeable = false;
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> lookups, then check inheritance anyway with the unknown type.
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, pname, info, getNativeType(UNKNOWN_TYPE));
}
// Check qualified name sets to 'object' and 'object.property'.
// This can sometimes handle cases when the type of 'object' is not known.
// e.g.,
// var obj = createUnknownType();
// /** @type {number} */ obj.foo = true;
JSType leftType = getJSType(lvalue);
if (lvalue.isQualifiedName()) {
// variable with inferred type case
Var var = t.getScope().getVar(lvalue.getQualifiedName());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() &&
t.getScope() != var.getScope()) {
// Don't look at "this.foo" variables from other scopes.
return;
}
if (var.getType() != null) {
leftType = var.getType();
}
}
}
// Fall through case for arbitrary LHS and arbitrary RHS.
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* After a struct object is created, we can't add new properties to it, with
* one exception. We allow creation of "static" properties like
* Foo.prototype.bar = baz;
* where Foo.prototype is a struct, if the assignment happens at the top level
* and the constructor Foo is defined in the same file.
*/
private void checkPropCreation(NodeTraversal t, Node lvalue) {
if (lvalue.isGetProp()) {
Node obj = lvalue.getFirstChild();
Node prop = lvalue.getLastChild();
JSType objType = getJSType(obj);
String pname = prop.getString();
if (!objType.isStruct() || objType.hasProperty(pname)) {
return;
}
Scope s = t.getScope();
if (obj.isThis() && getJSType(s.getRootNode()).isConstructor()) {
return;
}
// Prop created outside ctor, check that it's a static prop
Node assgnExp = lvalue.getParent();
Node assgnStm = assgnExp.getParent();
if (objType instanceof ObjectType &&
s.isGlobal() &&
NodeUtil.isPrototypePropertyDeclaration(assgnStm)) {
ObjectType instance =
objType.toObjectType().getOwnerFunction().getInstanceType();
String file = lvalue.getSourceFileName();
Node ctor = instance.getConstructor().getSource();
if (ctor != null && ctor.getSourceFileName().equals(file
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> childType, String propName,
NodeTraversal t, Node n) {
// If the property type is unknown, check the object type to see if it
// can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for
// properties where we've checked that it exists, or for properties on
// objects that aren't in this binary).
JSType propType = getJSType(n);
if (propType.isEquivalentTo(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
childType = childType.autobox();
ObjectType objectType = ObjectType.cast(childType);
if (objectType != null) {
// We special-case object types so that checks on enums can be
// much stricter, and so that we can use hasProperty (which is much
// faster in most cases).
if (!objectType.hasProperty(propName) ||
objectType.isEquivalentTo(
typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else {
checkPropertyAccessHelper(objectType, propName, t, n);
}
}
} else {
checkPropertyAccessHelper(childType, propName, t, n);
}
}
}
private void checkPropertyAccessHelper(JSType objectType, String propName,
NodeTraversal t, Node n) {
if (!objectType.isEmptyType() &&
reportMissingProperties && (!isPropertyTest(n) || objectType.isStruct())) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
case Token.CAST:
return isPropertyTest(parent);
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor =
baseInterfaceObj.getConstructor();
if (interfaceConstructor != null &&
!interfaceConstructor.isInterface()) {
badImplementedType = true;
}
} else {
badImplementedType = true;
}
if (badImplementedType) {
report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName);
}
}
// check properties
validator.expectAllInterfaceProperties(t, n, functionType);
}
} else if (functionType.isInterface()) {
// Interface must extend only interfaces
for (ObjectType extInterface : functionType.getExtendedInterfaces()) {
if (extInterface.getConstructor() != null
&& !extInterface.getConstructor().isInterface()) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE,
"interface", functionPrivateName));
}
}
// Check whether the extended interfaces have any conflicts
if (functionType.getExtendedInterfacesCount() > 1) {
// Only check when extending more than one interfaces
HashMap<String, ObjectType> properties
= new HashMap<String, ObjectType>();
HashMap<String, ObjectType> currentProperties
= new HashMap<String, ObjectType>();
for (ObjectType interfaceType : functionType.getExtendedInterfaces()) {
currentProperties.clear();
checkInterfaceConflictProperties(t, n, functionPrivateName,
properties, currentProperties, interfaceType);
properties.putAll(currentProperties);
}
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if (!childType.canBeCalled()) {
report(t, n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType.isFunctionType()) {
FunctionType functionType = childType.toMaybeFunctionType();
boolean isExtern = false;
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if (functionJSDocInfo != null &&
functionJSDocInfo.getAssociatedNode() != null) {
isExtern = functionJSDocInfo.getAssociatedNode().isFromExterns();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.is
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>Constructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explicit 'this' types must be called in a GETPROP
// or GETELEM.
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!(functionType.getTypeOfThis().toObjectType() != null &&
functionType.getTypeOfThis().toObjectType().isNativeObjectType()) &&
!(child.isGetElem() ||
child.isGetProp())) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO(nicksantos): Add something to check for calls of RegExp objects,
// which is not supported by IE. Either say something about the return type
// or warn about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
private void visitParameterList(NodeTraversal t, Node call,
FunctionType functionType) {
Iterator<Node> arguments = call.children().iterator();
arguments.next(); // skip the function name
Iterator<Node> parameters = functionType.getParameters().iterator();
int ordinal = 0;
Node parameter = null;
Node argument = null;
while (arguments.hasNext() &&
(parameters.hasNext() ||
parameter != null && parameter.isVarArgs())) {
// If there are no parameters left in the list, then the while loop
// above implies that this must be a var_args function.
if (parameters.hasNext()) {
parameter = parameters.next();
}
argument = arguments.next();
ordinal++;
validator.expectArgumentMatchesParameter(t, argument,
getJSType(argument), getJSType(parameter), call, ordinal);
}
int numArgs = call.getChildCount() - 1;
int minArgs = functionType.getMinArguments();
int maxArgs = functionType.getMaxArguments();
if (minArgs > numArgs || maxArgs < numArgs) {
report(t, call, WRONG_ARGUMENT_COUNT,
validator.getReadableJSTypeName(call.getFirstChild(), false),
String.valueOf(numArgs), String.valueOf(minArgs),
maxArgs != Integer.MAX_VALUE ?
" and no more than " + maxArgs + " argument(s)" : "");
}
}
/**
* Visits a RETURN node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* Models an assignment that defines a variable and the removal of it.
*
*/
class DefinitionsRemover {
/**
* @return an {@link Definition} object if the node contains a definition or
* {@code null} otherwise.
*/
static Definition getDefinition(Node n, boolean isExtern) {
// TODO(user): Since we have parent pointers handy. A lot of constructors
// can be simplified.
// This logic must match #isDefinitionNode
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) {
return new VarDefinition(n, isExtern);
} else if (parent.isFunction() && parent.getFirstChild() == n) {
if (!NodeUtil.isFunctionExpression(parent)) {
return new NamedFunctionDefinition(parent, isExtern);
} else if (!n.getString().equals("")) {
return new FunctionExpressionDefinition(parent, isExtern);
}
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return new AssignmentDefinition(parent, isExtern);
} else if (NodeUtil.isObjectLitKey(n)) {
return new ObjectLiteralPropertyDefinition(parent, n, n.getFirstChild(),
isExtern);
} else if (parent.isParamList()) {
Node function = parent.getParent();
return new FunctionArgumentDefinition(function, n, isExtern);
}
return null;
}
/**
* @return Whether a definition object can be created.
*/
static boolean isDefinitionNode(Node n) {
// This logic must match #getDefinition
Node parent = n.getParent();
if (parent == null) {
return false;
}
if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) {
return true;
} else if (parent.isFunction() && parent.getFirstChild
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>");
}
}
/**
* Represents an name-only external definition. The definition's
* RHS is missing.
*/
static final class ExternalNameOnlyDefinition extends IncompleteDefinition {
ExternalNameOnlyDefinition(Node lValue) {
super(lValue, true);
}
@Override
public void performRemove() {
throw new IllegalArgumentException(
"Can't remove external name-only definition");
}
}
/**
* Represents a function formal parameter. The definition's RHS is missing.
*/
static final class FunctionArgumentDefinition extends IncompleteDefinition {
FunctionArgumentDefinition(Node function,
Node argumentName,
boolean inExterns) {
super(argumentName, inExterns);
Preconditions.checkArgument(function.isFunction());
Preconditions.checkArgument(argumentName.isName());
}
@Override
public void performRemove() {
throw new IllegalArgumentException(
"Can't remove a FunctionArgumentDefinition");
}
}
/**
* Represents a function declaration or function expression.
*/
abstract static class FunctionDefinition extends Definition {
protected final Node function;
FunctionDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(node.isFunction());
function = node;
}
@Override
public Node getLValue() {
return function.getFirstChild();
}
@Override
public Node getRValue() {
return function;
}
}
/**
* Represents a function declaration without assignment node such as
* {@code function foo()}.
*/
static final class NamedFunctionDefinition extends FunctionDefinition {
NamedFunctionDefinition(Node node, boolean inExterns) {
super(node, inExterns);
}
@Override
public void performRemove() {
function.detachFromParent();
}
}
/**
* Represents a function expression that acts as a RHS. The defined
* name is only reachable from within the function.
*/
static final class FunctionExpressionDefinition extends FunctionDefinition {
FunctionExpressionDefinition(Node node, boolean inExterns) {
super(node, inExterns);
Preconditions.checkArgument(
NodeUtil.isFunctionExpression(node));
}
@Override
public void performRemove() {
// replace internal name with ""
function.replaceChild(function.getFirstChild(), IR.name(""));
}
}
/**
* Represents a declaration within an assignment.
*/
static final class AssignmentDefinition extends Definition {
private final Node assignment;
AssignmentDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(node.isAssign());
assignment = node;
}
@Override
public void performRemove() {
// A simple assignment. foo = bar() -> bar();
Node parent = assignment.getParent();
Node last = assignment.getLastChild();
assignment.removeChild(last);
parent.replaceChild(assignment, last);
}
@Override
public Node getLValue() {
return assignment.getFirstChild();
}
@Override
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
public Node getRValue() {
return assignment.getLastChild();
}
}
/**
* Represents member declarations using a object literal.
* Example: var x = { e : function() { } };
*/
static final class ObjectLiteralPropertyDefinition extends Definition {
private final Node literal;
private final Node name;
private final Node value;
ObjectLiteralPropertyDefinition(Node lit, Node name, Node value,
boolean isExtern) {
super(isExtern);
this.literal = lit;
this.name = name;
this.value = value;
}
@Override
public void performRemove() {
literal.removeChild(name);
}
@Override
public Node getLValue() {
// TODO(user) revisit: object literal definitions are an example
// of definitions whose LHS doesn't correspond to a node that
// exists in the AST. We will have to change the return type of
// getLValue sooner or later in order to provide this added
// flexibility.
switch (name.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// TODO(johnlenz): return a GETELEM for quoted strings.
return IR.getprop(
IR.objectlit(),
IR.string(name.getString()));
default:
throw new IllegalStateException("unexpected");
}
}
@Override
public Node getRValue() {
return value;
}
}
/**
* Represents a VAR declaration with an assignment.
*/
static final class VarDefinition extends Definition {
private final Node name;
VarDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(NodeUtil.isVarDeclaration(node));
Preconditions.checkArgument(node.hasChildren(),
"VAR Declaration of %sshould be assigned a value.", node.getString());
name = node;
}
@Override
public void performRemove() {
Node var = name.getParent();
Preconditions.checkState(var.getFirstChild() == var.getLastChild(),
"AST should be normalized first");
Node parent = var.getParent();
Node rValue = name.removeFirstChild();
Preconditions.checkState(!parent.isFor());
parent.replaceChild(var, NodeUtil.newExpr(rValue));
}
@Override
public Node getLValue() {
return name;
}
@Override
public Node getRValue() {
return name.getFirstChild();
}
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> fileoverview.
*/
private boolean handlePossibleFileOverviewJsDoc(
JsDocInfoParser jsDocParser) {
if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
return true;
}
return false;
}
private void handlePossibleFileOverviewJsDoc(Comment comment, Node irNode) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
handlePossibleFileOverviewJsDoc(jsDocParser);
}
private JSDocInfo handleJsDoc(AstNode node, Node irNode) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
JSDocInfo info = jsDocParser.retrieveAndResetParsedJSDocInfo();
if (info != null) {
validateTypeAnnotations(info, node);
}
return info;
}
}
return null;
}
private void validateTypeAnnotations(JSDocInfo info, AstNode node) {
if (info.hasType()) {
boolean valid = false;
switch (node.getType()) {
// Casts are valid
case com.google.javascript.rhino.head.Token.LP:
valid = node instanceof ParenthesizedExpression;
break;
// Variable declarations are valid
case com.google.javascript.rhino.head.Token.VAR:
valid = true;
break;
// Function declarations are valid
case com.google.javascript.rhino.head.Token.FUNCTION:
FunctionNode fnNode = (FunctionNode) node;
valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT;
break;
// Object literal properties and catch declarations are valid.
case com.google.javascript.rhino.head.Token.NAME:
valid = node.getParent() instanceof ObjectProperty
|| node.getParent() instanceof CatchClause
|| node.getParent() instanceof FunctionNode;
break;
// Object literal properties are valid
case com.google.javascript.rhino.head.Token.GET:
case com.google.javascript.rhino.head.Token.SET:
case com.google.javascript.rhino.head.Token.NUMBER:
case com.google.javascript.rhino.head.Token.STRING:
valid = node.getParent() instanceof ObjectProperty;
break;
// Property assignments are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.ASSIGN:
if (node instanceof Assignment) {
valid = isExprStmt(node.getParent())
&& isPropAccess(((Assignment) node).getLeft());
}
break;
// Property definitions are valid, if at the root of an expression.
case com.
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>google.javascript.rhino.head.Token.GETPROP:
case com.google.javascript.rhino.head.Token.GETELEM:
valid = isExprStmt(node.getParent());
break;
case com.google.javascript.rhino.head.Token.CALL:
valid = info.isDefine();
break;
}
if (!valid) {
errorReporter.warning(MISPLACED_TYPE_ANNOTATION,
sourceName,
node.getLineno(), "", 0);
}
}
}
private boolean isPropAccess(AstNode node) {
return node.getType() == com.google.javascript.rhino.head.Token.GETPROP
|| node.getType() == com.google.javascript.rhino.head.Token.GETELEM;
}
private boolean isExprStmt(AstNode node) {
return node.getType() == com.google.javascript.rhino.head.Token.EXPR_RESULT
|| node.getType() == com.google.javascript.rhino.head.Token.EXPR_VOID;
}
private Node transform(AstNode node) {
Node irNode = justTransform(node);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode = maybeInjectCastNode(node, jsDocInfo, irNode);
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node maybeInjectCastNode(AstNode node, JSDocInfo info, Node irNode) {
if (node.getType() == com.google.javascript.rhino.head.Token.LP
&& node instanceof ParenthesizedExpression
&& info.hasType()) {
irNode = newNode(Token.CAST, irNode);
}
return irNode;
}
/**
* Parameter NAMEs are special, because they can have inline type docs
* attached.
*
* function f(/** string */ x) {}
* annotates 'x' as a string.
*
* @see http://code.google.com/p/jsdoc-toolkit/wiki/InlineDocs
*/
private Node transformParameter(AstNode node) {
Node irNode = justTransform(node);
Comment comment = node.getJsDocNode();
if (comment != null) {
JSDocInfo info = parseInlineTypeDoc(comment, irNode);
if (info != null) {
irNode.setJSDocInfo(info);
}
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNameAsString(Name node) {
Node irNode = transformDispatcher.processName(node, true);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSource
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>AbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser parser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
irNode,
config,
errorReporter);
return parser.parseInlineTypeDoc();
}
// Set the length on the node if we're in IDE mode.
private void maybeSetLengthFrom(Node node, AstNode source) {
if (config.isIdeMode) {
node.setLength(source.getLength());
}
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.rhino.head.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.rhino.head.Node child : n) {
node.addChildToBack(transform((AstNode) child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret;
if (n instanceof Name) {
ret = transformNameAsString((Name) n);
} else if (n instanceof NumberLiteral) {
ret = transformNumberAsString((NumberLiteral) n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else {
ret = transform(n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
}
Preconditions.checkState(ret.isString());
return ret;
}
@Override
Node processArrayLiteral(ArrayLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.ARRAYLIT);
for (AstNode child : literalNode.getElements()) {
Node c = transform(child);
node.addChildToBack(c);
}
return node;
}
@Override
Node processAssignment(Assignment
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> assignmentNode) {
Node assign = processInfixExpression(assignmentNode);
Node target = assign.getFirstChild();
if (!validAssignmentTarget(target)) {
errorReporter.error(
"invalid assignment target",
sourceName,
target.getLineno(), "", 0);
}
return assign;
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.rhino.head.Node child : rootNode) {
node.addChildToBack(transform((AstNode) child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* ECMA-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) {
return false;
}
int nType = n.getType();
return nType == Token.EXPR_RESULT &&
n.getFirstChild().isString() &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getCatchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return newNode(
Token
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processEmptyStatement(EmptyStatement exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.getType()));
node.addChildToBack(transform(statementNode.getExpression()));
return node;
}
@Override
Node processForInLoop(ForInLoop loopNode) {
if (loopNode.isForEach()) {
errorReporter.error(
"unsupported language extension: for each",
sourceName,
loopNode.getLineno(), "", 0);
// Return the bare minimum to put the AST in a valid state.
return newNode(Token.EXPR_RESULT, Node.newNumber(0));
}
return newNode(
Token.FOR,
transform(loopNode.getIterator()),
transform(loopNode.getIteratedObject()),
transformBlock(loopNode.getBody()));
}
@Override
Node processForLoop(ForLoop loopNode) {
Node node = newNode(
Token.FOR,
transform(loopNode.getInitializer()),
transform(loopNode.getCondition()),
transform(loopNode.getIncrement()));
node.addChildToBack(transformBlock(loopNode.getBody()));
return node;
}
@Override
Node processFunctionCall(FunctionCall callNode) {
Node node = newNode(transformTokenType(callNode.getType()),
transform(callNode.getTarget()));
for (AstNode child : callNode.getArguments()) {
node.addChildToBack(transform(child));
}
node.setLineno(node.getFirstChild().getLineno());
node.setCharno(node.getFirstChild().getCharno());
maybeSetLengthFrom(node, callNode);
return node;
}
@Override
Node processFunctionNode(FunctionNode functionNode) {
Name name = functionNode.getFunctionName();
Boolean isUnnamedFunction
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> private boolean isReservedKeyword(String identifier) {
return reservedKeywords != null && reservedKeywords.contains(identifier);
}
@Override
Node processNewExpression(NewExpression exprNode) {
Node node = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getTarget()));
for (AstNode child : exprNode.getArguments()) {
node.addChildToBack(transform(child));
}
node.setLineno(exprNode.getLineno());
node.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(node, exprNode);
return node;
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
key.setType(Token.STRING_KEY);
Node value = transform(el.getRight());
if (el.isGetter()) {
key.setType(Token.GETTER_DEF);
Preconditions.checkState(value.isFunction());
if (getFnParamNode(value).hasChildren()) {
reportGetterParam(el.getLeft());
}
} else if (el.isSetter()) {
key.setType(Token.SETTER_DEF);
Preconditions.checkState(value.isFunction());
if (!getFnParamNode(value).hasOneChild()) {
reportSetterParam(el.getLeft());
}
}
key.addChildToFront(value);
node.addChildToBack(key);
}
return node;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
Node getFnParamNode(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
Node leftChild = transform(getNode.getTarget());
Node newNode = newNode(
Token.GETPROP, leftChild, transformAsString(getNode.getProperty()));
newNode.setLineno(leftChild.get
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>, transform(expr));
}
Node block = newNode(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.setCharno(position2charno(caseNode.getAbsolutePosition()));
maybeSetLengthFrom(block, caseNode);
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = newNode(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return newNode(Token.THROW,
transform(statementNode.getExpression()));
}
@Override
Node processTryStatement(TryStatement statementNode) {
Node node = newNode(Token.TRY,
transformBlock(statementNode.getTryBlock()));
Node block = newNode(Token.BLOCK);
boolean lineSet = false;
for (CatchClause cc : statementNode.getCatchClauses()) {
// Mark the enclosing block at the same line as the first catch
// clause.
if (lineSet == false) {
block.setLineno(cc.getLineno());
maybeSetLengthFrom(block, cc);
lineSet = true;
}
block.addChildToBack(transform(cc));
}
node.addChildToBack(block);
AstNode finallyBlock = statementNode.getFinallyBlock();
if (finallyBlock != null) {
node.addChildToBack(transformBlock(finallyBlock));
}
// If we didn't set the line on the catch clause, then
// we've got an empty catch clause. Set its line to be the same
// as the finally block (to match Old Rhino's behavior.)
if ((lineSet == false) && (finallyBlock != null)) {
block.setLineno(finallyBlock.getLineno());
maybeSetLengthFrom(block, finallyBlock);
}
return node;
}
@Override
Node processUnaryExpression(UnaryExpression exprNode) {
int type = transformTokenType(exprNode.getType());
Node operand = transform(exprNode.getOperand());
if (type == Token.NEG && operand.isNumber()) {
operand.setDouble(-operand.getDouble());
return operand;
} else {
if (type == Token.DELPROP &&
!(operand.isGetProp() ||
operand.isGetElem() ||
operand.isName())) {
String msg =
"Invalid delete operand. Only properties can be deleted.";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>);
} else if (type == Token.INC || type == Token.DEC) {
if (!validAssignmentTarget(operand)) {
String msg = (type == Token.INC)
? "invalid increment target"
: "invalid decrement target";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
}
}
Node node = newNode(type, operand);
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
}
return node;
}
}
private boolean validAssignmentTarget(Node target) {
switch (target.getType()) {
case Token.CAST: // CAST is a bit weird, but syntactically valid.
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
return true;
}
return false;
}
@Override
Node processVariableDeclaration(VariableDeclaration declarationNode) {
if (!config.acceptConstKeyword && declarationNode.getType() ==
com.google.javascript.rhino.head.Token.CONST) {
processIllegalToken(declarationNode);
}
Node node = newNode(Token.VAR);
for (VariableInitializer child : declarationNode.getVariables()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processVariableInitializer(VariableInitializer initializerNode) {
Node node = transform(initializerNode.getTarget());
if (initializerNode.getInitializer() != null) {
Node initalizer = transform(initializerNode.getInitializer());
node.addChildToBack(initalizer);
}
return node;
}
@Override
Node processWhileLoop(WhileLoop loopNode) {
return newNode(
Token.WHILE,
transform(loopNode.getCondition()),
transformBlock(loopNode.getBody()));
}
@Override
Node processWithStatement(WithStatement statementNode) {
return newNode(
Token.WITH,
transform(statementNode.getExpression()),
transformBlock(statementNode.getStatement()));
}
@Override
Node processIllegalToken(AstNode node) {
errorReporter.error(
"Unsupported syntax: " +
com.google.javascript.rhino.head.Token.typeToName(
node.getType()),
sourceName,
node.getLineno(), "", 0);
return newNode(Token.EMPTY);
}
void reportDestructuringAssign(AstNode node) {
errorReporter.error(
"destructuring assignment forbidden",
sourceName,
node.getLineno(), "", 0);
}
void reportGetter(AstNode node) {
errorReporter.error(
GETTER_ERROR_MESSAGE,
sourceName,
node.getLineno(), "", 0);
}
void reportSetter(AstNode node) {
errorReporter.error(
SETTER_ERROR_MESSAGE,
sourceName,
node.getLineno(), "", 0);
}
void report
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
ReferenceCollectingCallback callback = new ReferenceCollectingCallback(
compiler, new ReferenceCheckingBehavior());
callback.hotSwapScript(scriptRoot, originalRoot);
}
/**
* Behavior that checks variables for redeclaration or early references
* just after they go out of scope.
*/
private class ReferenceCheckingBehavior implements Behavior {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {
// TODO(bashir) In hot-swap version this means that for global scope we
// only go through all global variables accessed in the modified file not
// all global variables. This should be fixed.
// Check all vars after finishing a scope
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
checkVar(v, referenceMap.getReferences(v).references);
}
}
/**
* If the variable is declared more than once in a basic block, generate a
* warning. Also check if a variable is used in a given scope before it is
* declared, which suggest a likely error. Relies on the fact that
* references is in parse-tree order.
*/
private void checkVar(Var v, List<Reference> references) {
blocksWithDeclarations.clear();
boolean isDeclaredInScope = false;
boolean isUnhoistedNamedFunction = false;
Reference hoistedFn = null;
// Look for hoisted functions.
for (Reference reference : references) {
if (reference.isHoistedFunction()) {
blocksWithDeclarations.add(reference.getBasicBlock());
isDeclaredInScope = true;
hoistedFn = reference;
break;
} else if (NodeUtil.isFunctionDeclaration(
reference.getNode().getParent())) {
isUnhoistedNamedFunction = true;
}
}
for (Reference reference : references) {
if (reference == hoistedFn) {
continue;
}
BasicBlock basicBlock = reference.getBasicBlock();
boolean isDeclaration = reference.isDeclaration();
boolean allowDupe =
SyntacticScopeCreator.hasDuplicateDeclarationSuppression(
reference.getNode(), v);
if (isDeclaration && !allowDupe) {
// Look through all the declarations we've found so far, and
// check if any of them are before this block.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (declaredBlock.provablyExecutesBefore(basicBlock)) {
// TODO(johnlenz): Fix AST generating clients that so they would
// have property StaticSourceFile attached at each node. Or
// better yet, make sure the generated code never violates
// the requirement to pass aggressive var check!
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> (inputsById.containsKey(ast.getInputId())) {
throw new IllegalArgumentException("Conflicting externs name: " + name);
}
CompilerInput input = new CompilerInput(ast, true);
putCompilerInput(input.getInputId(), input);
externsRoot.addChildToFront(ast.getAstRoot(this));
externs.add(0, input);
return input;
}
private CompilerInput putCompilerInput(InputId id, CompilerInput input) {
input.setCompiler(this);
return inputsById.put(id, input);
}
/** Add a source input dynamically. Intended for incremental compilation. */
void addIncrementalSourceAst(JsAst ast) {
InputId id = ast.getInputId();
Preconditions.checkState(getInput(id) == null, "Duplicate input %s", id.getIdName());
putCompilerInput(id, new CompilerInput(ast));
}
/**
* Replace a source input dynamically. Intended for incremental
* re-compilation.
*
* If the new source input doesn't parse, then keep the old input
* in the AST and return false.
*
* @return Whether the new AST was attached successfully.
*/
boolean replaceIncrementalSourceAst(JsAst ast) {
CompilerInput oldInput = getInput(ast.getInputId());
Preconditions.checkNotNull(oldInput, "No input to replace: %s", ast.getInputId().getIdName());
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
Node oldRoot = oldInput.getAstRoot(this);
if (oldRoot != null) {
oldRoot.getParent().replaceChild(oldRoot, newRoot);
} else {
getRoot().getLastChild().addChildToBack(newRoot);
}
CompilerInput newInput = new CompilerInput(ast);
putCompilerInput(ast.getInputId(), newInput);
JSModule module = oldInput.getModule();
if (module != null) {
module.addAfter(newInput, oldInput);
module.remove(oldInput);
}
// Verify the input id is set properly.
Preconditions.checkState(
newInput.getInputId().equals(oldInput.getInputId()));
InputId inputIdOnAst = newInput.getAstRoot(this).getInputId();
Preconditions.checkState(newInput.getInputId().equals(inputIdOnAst));
inputs.remove(oldInput);
return true;
}
/**
* Add a new source input dynamically. Intended for incremental compilation.
* <p>
* If the new source input doesn't parse, it will not be added, and a false
* will be returned.
*
* @param ast the JS Source to add.
* @return true if the source was added successfully, false otherwise.
* @throws IllegalStateException if an input for this ast already exists.
*/
boolean addNewSourceAst(JsAst ast) {
CompilerInput oldInput = getInput(ast.getInputId
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top-level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if (!allowDupe) {
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
if (info != null && info.getSuppressions().contains("duplicate")) {
return true;
}
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
return (info != null && info.getSuppressions().
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
"identifiers ending in '__' cannot be used in Caja");
static final DiagnosticType DUPLICATE_OBJECT_KEY = DiagnosticType.warning(
"JSC_DUPLICATE_OBJECT_KEY",
"object literals cannot contain duplicate keys in ES5 strict mode");
static final DiagnosticType BAD_FUNCTION_DECLARATION = DiagnosticType.error(
"JSC_BAD_FUNCTION_DECLARATION",
"functions can only be declared at top level or immediately within " +
"another function in ES5 strict mode");
private final AbstractCompiler compiler;
private final boolean noVarCheck;
private final boolean noCajaChecks;
StrictModeCheck(AbstractCompiler compiler) {
this(compiler, false, false);
}
StrictModeCheck(
AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) {
this.compiler = compiler;
this.noVarCheck = noVarCheck;
this.noCajaChecks = noCajaChecks;
}
@Override public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
NodeTraversal.traverse(compiler, root, new NonExternChecks());
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
checkFunctionUse(t, n);
} else if (n.isName()) {
if (!isDeclaration(n)) {
checkNameUse(t, n);
}
} else if (n.isAssign()) {
checkAssignment(t, n);
} else if (n.isDelProp()) {
checkDelete(t, n);
} else if (n.isObjectLit()) {
checkObjectLiteral(t, n);
} else if (n.isLabel()) {
checkLabel(t, n);
}
}
/** Checks that the function is used legally. */
private void checkFunctionUse(NodeTraversal t, Node n) {
if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) {
t.report(n, BAD_FUNCTION_DECLARATION);
}
}
/**
* Determines if the given name is a declaration, which can be a declaration
* of a variable, function, or argument.
*/
private static boolean isDeclaration(Node n) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.CATCH:
return true;
case Token.PARAM_LIST:
return n.getParent().getParent().isFunction();
default:
return false;
}
}
/** Checks that the given name is used legally. */
private void checkNameUse(NodeTraversal t, Node n) {
Var v = t.getScope().getVar(n.getString());
if (v == null) {
// In particular, this prevents creating a global variable by assigning
// to it without a declaration.
if (!no
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> msgNode != null && msgNode.isCall();
if (!isMessageName(messageKey, isNewStyleMessage)) {
return;
}
if (msgNode == null) {
compiler.report(
traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey));
return;
}
// Just report a warning if a qualified messageKey that looks like a message
// (e.g. "a.b.MSG_X") doesn't use goog.getMsg().
if (isNewStyleMessage) {
googMsgNodes.remove(msgNode);
} else if (style != JsMessage.Style.LEGACY) {
compiler.report(traversal.makeError(node, checkLevel,
MESSAGE_NOT_INITIALIZED_USING_NEW_SYNTAX));
}
boolean isUnnamedMsg = isUnnamedMessageName(messageKey);
Builder builder = new Builder(
isUnnamedMsg ? null : messageKey);
builder.setSourceName(traversal.getSourceName());
try {
if (isVar) {
extractMessageFromVariable(builder, node, parent, parent.getParent());
} else {
extractMessageFromProperty(builder, node.getFirstChild(), node);
}
} catch (MalformedException ex) {
compiler.report(traversal.makeError(ex.getNode(),
MESSAGE_TREE_MALFORMED, ex.getMessage()));
return;
}
JsMessage extractedMessage = builder.build(idGenerator);
// If asked to check named internal messages.
if (needToCheckDuplications
&& !isUnnamedMsg
&& !extractedMessage.isExternal()) {
checkIfMessageDuplicated(messageKey, msgNode);
}
trackMessage(traversal, extractedMessage,
messageKey, msgNode, isUnnamedMsg);
if (extractedMessage.isEmpty()) {
// value of the message is an empty string. Translators do not like it.
compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_TEXT,
messageKey));
}
// New-style messages must have descriptions. We don't emit a warning
// for legacy-style messages, because there are thousands of
// them in legacy code that are not worth the effort to fix, since they've
// already been translated anyway.
String desc = extractedMessage.getDesc();
if (isNewStyleMessage
&& (desc == null || desc.trim().isEmpty())
&& !extractedMessage.isExternal()) {
compiler.report(traversal.makeError(node, MESSAGE_HAS_NO_DESCRIPTION,
messageKey));
}
JsMessageDefinition msgDefinition = new JsMessageDefinition(
node, msgNode, msgNodeParent);
processJsMessage(extractedMessage, msgDefinition);
}
/**
* Track a message for later retrieval.
*
* This is used for tracking duplicates, and for figuring out message
* fallback. Not all message types are trackable, because that would
* require a more sophisticated analysis. e.g.,
* function f(s) {
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>Child();
String desc = extractStringFromStringExprNode(valueNode);
if (desc.startsWith(HIDDEN_DESC_PREFIX)) {
builder.setDesc(desc.substring(HIDDEN_DESC_PREFIX.length()).trim());
builder.setIsHidden(true);
} else {
builder.setDesc(desc);
}
return true;
}
}
return false;
}
/**
* Initializes the meta data in a message builder given a node that may
* contain JsDoc properties.
*
* @param builder the message builder whose meta data will be initialized
* @param node the node with the message's JSDoc properties
* @return true if message has JsDoc with valid description in @desc
* annotation
*/
private boolean maybeInitMetaDataFromJsDoc(Builder builder, Node node) {
boolean messageHasDesc = false;
JSDocInfo info = node.getJSDocInfo();
if (info != null) {
String desc = info.getDescription();
if (desc != null) {
builder.setDesc(desc);
messageHasDesc = true;
}
if (info.isHidden()) {
builder.setIsHidden(true);
}
if (info.getMeaning() != null) {
builder.setMeaning(info.getMeaning());
}
}
return messageHasDesc;
}
/**
* Returns the string value associated with a node representing a JS string or
* several JS strings added together (e.g. {@code 'str'} or {@code 's' + 't' +
* 'r'}).
*
* @param node the node from where we extract the string
* @return String representation of the node
* @throws MalformedException if the parsed message is invalid
*/
private static String extractStringFromStringExprNode(Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
return node.getString();
case Token.ADD:
StringBuilder sb = new StringBuilder();
for (Node child : node.children()) {
sb.append(extractStringFromStringExprNode(child));
}
return sb.toString();
default:
throw new MalformedException("STRING or ADD node expected; found: " +
getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a FUNCTION node.
* <p>
* <pre>
* The tree should look something like:
*
* function
* |-- name
* |-- lp
* | |-- name <arg1>
* | -- name <arg2>
* -- block
* |
* --return
* |
* --add
* |-- string foo
* -- name <arg1>
* </pre>
*
* @param builder the message builder
* @param node the function node that contains a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromFunctionNode(Builder builder
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>, Node node)
throws MalformedException {
Set<String> phNames = Sets.newHashSet();
for (Node fnChild : node.children()) {
switch (fnChild.getType()) {
case Token.NAME:
// This is okay. The function has a name, but it is empty.
break;
case Token.PARAM_LIST:
// Parse the placeholder names from the function argument list.
for (Node argumentNode : fnChild.children()) {
if (argumentNode.isName()) {
String phName = argumentNode.getString();
if (phNames.contains(phName)) {
throw new MalformedException("Duplicate placeholder name: "
+ phName, argumentNode);
} else {
phNames.add(phName);
}
}
}
break;
case Token.BLOCK:
// Build the message's value by examining the return statement
Node returnNode = fnChild.getFirstChild();
if (!returnNode.isReturn()) {
throw new MalformedException("RETURN node expected; found: "
+ getReadableTokenName(returnNode), returnNode);
}
for (Node child : returnNode.children()) {
extractFromReturnDescendant(builder, child);
}
// Check that all placeholders from the message text have appropriate
// object literal keys
for (String phName : builder.getPlaceholders()) {
if (!phNames.contains(phName)) {
throw new MalformedException(
"Unrecognized message placeholder referenced: " + phName,
returnNode);
}
}
break;
default:
throw new MalformedException(
"NAME, LP, or BLOCK node expected; found: "
+ getReadableTokenName(node), fnChild);
}
}
}
/**
* Appends value parts to the message builder by traversing the descendants
* of the given RETURN node.
*
* @param builder the message builder
* @param node the node from where we extract a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromReturnDescendant(Builder builder, Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
builder.appendStringPart(node.getString());
break;
case Token.NAME:
builder.appendPlaceholderReference(node.getString());
break;
case Token.ADD:
for (Node child : node.children()) {
extractFromReturnDescendant(builder, child);
}
break;
default:
throw new MalformedException(
"STRING, NAME, or ADD node expected; found: "
+ getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a CALL node.
* <p>
* The tree should look something like:
*
* <pre>
* call
* |-- getprop
* | |-- name 'goog'
* | +-- string 'getMsg'
* |
* |-- string
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
printErrorLocations(errors, jsType);
if (!errors.isEmpty()) {
suggestion = "Consider fixing errors for the following types:\n";
suggestion += Joiner.on("\n").join(errors);
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()),
n.toString(), suggestion));
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this
// case right now.
if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(
t.getSourceName(), child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString(), ""));
}
}
child = child.getNext();
}
}
private void printErrorLocations(List<String> errors, JSType t) {
if (!t.isObject() || t.isAllType()) {
return;
}
if (t.isUnionType()) {
for (JSType alt : t.toMaybeUnionType().getAlternates()) {
printErrorLocations(errors, alt);
}
return;
}
for (JSError error : invalidationMap.get(t)) {
if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) {
return;
}
errors.add(
t.toString() + " at " + error.sourceName + ":" + error.lineNumber);
}
}
/**
* Processes a property, adding it to the list of properties to rename.
* @return a representative type for the property reference, which will be
* the highest type on the prototype chain of the provided type. In the
* case of a union type, it will be the highest type on the prototype
* chain of one of the members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
type = typeSystem.restrictByNotNullOrUndefined(type);
if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) {
return null;
}
Iterable<T> alternatives = typeSystem.getTypeAlternatives(type);
if (
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
private final JSTypeRegistry registry;
TypedCodeGenerator(
CodeConsumer consumer, CompilerOptions options, JSTypeRegistry registry) {
super(consumer, options);
Preconditions.checkNotNull(registry);
this.registry = registry;
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.isBlock()
|| parent.isScript())) {
if (n.isFunction()) {
add(getFunctionAnnotation(n));
} else if (n.isExprResult()
&& n.getFirstChild().isAssign()) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.isVar()
&& n.getFirstChild().getFirstChild() != null) {
add(getTypeAnnotation(n.getFirstChild().getFirstChild()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
// Only add annotations for things with JSDoc, or function literals.
JSDocInfo jsdoc = NodeUtil.getBestJSDocInfo(node);
if (jsdoc == null && !node.isFunction()) {
return "";
}
JSType type = node.getJSType();
if (type == null) {
return "";
} else if (type.isFunctionType()) {
return getFunctionAnnotation(node);
} else if (type.
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> been deprecated: {2}");
static final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS",
"Class {0} has been deprecated.");
static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS_REASON",
"Class {0} has been deprecated: {1}");
static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_GLOBAL_ACCESS",
"Access to private variable {0} not allowed outside file {1}.");
static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_PROPERTY_ACCESS",
"Access to private property {0} of {1} not allowed here.");
static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PROTECTED_PROPERTY_ACCESS",
"Access to protected property {0} of {1} not allowed here.");
static final DiagnosticType PRIVATE_OVERRIDE =
DiagnosticType.disabled(
"JSC_PRIVATE_OVERRIDE",
"Overriding private property of {0}.");
static final DiagnosticType EXTEND_FINAL_CLASS =
DiagnosticType.error(
"JSC_EXTEND_FINAL_CLASS",
"{0} is not allowed to extend final class {1}.");
static final DiagnosticType VISIBILITY_MISMATCH =
DiagnosticType.disabled(
"JSC_VISIBILITY_MISMATCH",
"Overriding {0} property of {1} with {2} property.");
static final DiagnosticType CONST_PROPERTY_REASSIGNED_VALUE =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_REASSIGNED_VALUE",
"constant property {0} assigned a value more than once");
static final DiagnosticType CONST_PROPERTY_DELETED =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_DELETED",
"constant property {0} cannot be deleted");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
private final Multimap<String, String> initializedConstantProperties;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.initializedConstantProperties = HashMultimap.create();
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
@Override
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n)) {
deprecated
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE) {
StaticSourceFile varSrc = var.getSourceFile();
StaticSourceFile refSrc = name.getStaticSourceFile();
if (varSrc != null &&
refSrc != null &&
!varSrc.getName().equals(refSrc.getName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), varSrc.getName()));
}
}
}
}
}
/**
* Checks if a constructor is trying to override a final class.
*/
private void checkFinalClassOverrides(NodeTraversal t, Node fn, Node parent) {
JSType type = fn.getJSType().toMaybeFunctionType();
if (type != null && type.isConstructor()) {
JSType finalParentClass = getFinalParentClass(getClassOfMethod(fn, parent));
if (finalParentClass != null) {
compiler.report(
t.makeError(fn, EXTEND_FINAL_CLASS,
type.getDisplayName(), finalParentClass.getDisplayName()));
}
}
}
/**
* Determines whether the given property with @const tag got reassigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.isDelProp();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& !parent.isInc() && !parent.isDec()
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>isNew())) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.isGetProp() && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently OK to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.isAssign() &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n) {
if (n.isFunction()) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns if a property is declared constant.
*/
private static boolean isPropertyDeclaredConstant(
ObjectType objectType, String prop) {
for (;
// Only objects with reference names can have constant properties.
objectType != null && objectType.hasReferenceName();
objectType = objectType.getImplicitPrototype()) {
JSDocInfo docInfo = objectType.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isConstant()) {
return true;
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(getTypedScopeCreator());
TypeCheck check = makeTypeCheck(compiler);
check.process(externs, root);
compiler.getErrorManager().setTypedPercent(check.getTypedPercent());
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
makeTypeCheck(compiler).check(scriptRoot, false);
}
};
}
};
/**
* Checks possible execution paths of the program for problems: missing return
* statements and dead code.
*/
final HotSwapPassFactory checkControlFlow =
new HotSwapPassFactory("checkControlFlow", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
List<Callback> callbacks = Lists.newArrayList();
if (options.checkUnreachableCode.isOn()) {
callbacks.add(
new CheckUnreachableCode(compiler, options.checkUnreachableCode));
}
if (options.checkMissingReturn.isOn() && options.checkTypes) {
callbacks.add(
new CheckMissingReturn(compiler, options.checkMissingReturn));
}
return combineChecks(compiler, callbacks);
}
};
/** Checks access controls. Depends on type-inference. */
final HotSwapPassFactory checkAccessControls =
new HotSwapPassFactory("checkAccessControls", true) {
@Override
protected HotSwapCompilerPass create(AbstractCompiler compiler) {
return new CheckAccessControls(compiler);
}
};
/** Executes the given callbacks with a {@link CombinedCompilerPass}. */
private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler,
List<Callback> callbacks) {
Preconditions.checkArgument(callbacks.size() > 0);
Callback[] array = callbacks.toArray(new Callback[callbacks.size()]);
return new CombinedCompilerPass(compiler, array);
}
/** A compiler pass that resolves types in the global scope. */
class GlobalTypeResolver implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
GlobalTypeResolver(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (topScope == null) {
regenerateGlobalTypedScope(compiler, root.getParent());
} else {
compiler.getTypeRegistry().resolveTypesInScope(topScope);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
patchGlobalTypedScope(compiler, scriptRoot);
}
}
/** A compiler pass that clears the global scope. */
class ClearTypedScope implements CompilerPass {
@Override
public void process(Node externs, Node root) {
clearTypedScope();
}
}
/** Checks global name usage. */
final PassFactory checkGlobalNames =
new PassFactory("checkGlobalNames", true) {
@Override
protected CompilerPass create(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node extern
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>;
// The node representing the "this" value, maybe null
final Node thisValue;
// The head of a Node list representing the parameters
final Node parameters;
public Bind(Node target, Node thisValue, Node parameters) {
this.target = target;
this.thisValue = thisValue;
this.parameters = parameters;
}
/**
* The number of parameters bound (not including the 'this' value).
*/
int getBoundParameterCount() {
if (parameters == null) {
return 0;
}
Node paramParent = parameters.getParent();
return paramParent.getChildCount() -
paramParent.getIndexOfChild(parameters);
}
}
/**
* Whether this CALL function is testing for the existence of a property.
*/
public boolean isPropertyTestFunction(Node call);
/**
* Whether this GETPROP node is an alias for an object prototype.
*/
public boolean isPrototypeAlias(Node getProp);
/**
* Checks if the given method performs a object literal cast, and if it does,
* returns information on the cast. By default, always returns null. Meant
* to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public ObjectLiteralCast getObjectLiteralCast(Node callNode);
/**
* Gets a collection of all properties that are defined indirectly on global
* objects. (For example, Closure defines superClass_ in the goog.inherits
* call).
*/
public Collection<String> getIndirectlyDeclaredProperties();
/**
* Returns the set of AssertionFunction.
*/
public Collection<AssertionFunctionSpec> getAssertionFunctions();
/** Specify the kind of inheritance */
static enum SubclassType {
INHERITS,
MIXIN
}
/** Record subclass relations */
static class SubclassRelationship {
final SubclassType type;
final String subclassName;
final String superclassName;
public SubclassRelationship(SubclassType type,
Node subclassNode, Node superclassNode) {
this.type = type;
this.subclassName = subclassNode.getQualifiedName();
this.superclassName = superclassNode.getQualifiedName();
}
}
/**
* Delegates provides a mechanism and structure for identifying where classes
* can call out to optional code to augment their functionality. The optional
* code is isolated from the base code through the use of a subclass in the
* optional code derived from the delegate class in the base code.
*/
static class DelegateRelationship {
/** The subclass in the base code. */
final String delegateBase;
/** The class in the base code. */
final String delegator;
DelegateRelationship(String delegateBase, String delegator) {
this.delegateBase = delegateBase;
this.delegator = delegator;
}
}
/**
* An object literal cast provides a mechanism to cast object literals to
* other types without a warning.
*/
static class ObjectLiteralCast {
/** Type to cast to. */
final String typeName;
/** Object to cast. */
final Node objectNode
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> */
private static boolean definitionTypeContainsFunctionType(Definition def) {
Node rhs = def.getRValue();
if (rhs == null) {
return true;
}
switch (rhs.getType()) {
case Token.ASSIGN:
case Token.AND:
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.FUNCTION:
case Token.HOOK:
case Token.NAME:
case Token.NEW:
case Token.OR:
return true;
default:
return false;
}
}
/**
* Get the value of the @nosideeffects annotation stored in the
* doc info.
*/
private static boolean hasNoSideEffectsAnnotation(Node node) {
JSDocInfo docInfo = node.getJSDocInfo();
return docInfo != null && docInfo.isNoSideEffects();
}
/**
* Gather function nodes that have @nosideeffects annotations.
*/
private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback {
private final boolean inExterns;
GatherNoSideEffectFunctions(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!inExterns && hasNoSideEffectsAnnotation(node)) {
traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
if (node.isGetProp()) {
if (parent.isExprResult() &&
hasNoSideEffectsAnnotation(node)) {
noSideEffectFunctionNames.add(node);
}
} else if (node.isFunction()) {
// The annotation may attached to the function node, the
// variable declaration or assignment expression.
boolean hasAnnotation = hasNoSideEffectsAnnotation(node);
List<Node> nameNodes = Lists.newArrayList();
nameNodes.add(node.getFirstChild());
if (parent.isName()) {
Node gramp = parent.getParent();
if (gramp.isVar() &&
gramp.hasOneChild() &&
hasNoSideEffectsAnnotation(gramp)) {
hasAnnotation = true;
}
nameNodes.add(parent);
} else if (parent.isAssign()) {
if (hasNoSideEffectsAnnotation(parent)) {
hasAnnotation = true;
}
nameNodes.add(parent.getFirstChild());
}
if (hasAnnotation) {
noSideEffectFunctionNames.addAll(nameNodes);
}
}
}
}
/**
* Set the no side effects property for CALL and NEW nodes that
* refer to function names that are known to have no side effects.
*/
private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback {
private final SimpleDefinitionFinder defFinder;
SetNoSideEffectCallProperty(SimpleDefinitionFinder defFinder) {
this.defFinder = defFinder;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>dropStub) {
// Incomplete definition
Definition definition = new ExternalNameOnlyDefinition(node);
nameDefinitionMultimap.put(name, definition);
definitionSiteMap.put(node,
new DefinitionSite(node,
definition,
traversal.getModule(),
traversal.inGlobalScope(),
inExterns));
}
}
}
}
/**
* @return Whether the node has a JSDoc that actually declares something.
*/
private boolean jsdocContainsDeclarations(Node node) {
JSDocInfo info = node.getJSDocInfo();
return (info != null && info.containsDeclaration());
}
}
private class UseSiteGatheringCallback extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
Collection<Definition> defs = getDefinitionsReferencedAt(node);
if (defs == null) {
return;
}
Definition first = defs.iterator().next();
String name = getSimplifiedName(first.getLValue());
Preconditions.checkNotNull(name);
nameUseSiteMultimap.put(
name,
new UseSite(node, traversal.getScope(), traversal.getModule()));
}
}
/**
* @param use A use site to check.
* @return Whether the use is a call or new.
*/
static boolean isCallOrNewSite(UseSite use) {
Node call = use.node.getParent();
if (call == null) {
// The node has been removed from the AST.
return false;
}
// We need to make sure we're dealing with a call to the function we're
// optimizing. If the the first child of the parent is not the site, this
// is a nested call and it's a call to another function.
return NodeUtil.isCallOrNew(call) && call.getFirstChild() == use.node;
}
boolean canModifyDefinition(Definition definition) {
if (isExported(definition)) {
return false;
}
// Don't modify unused definitions for two reasons:
// 1) It causes unnecessary churn
// 2) Other definitions might be used to reflect on this one using
// goog.reflect.object (the check for definitions with uses is below).
Collection<UseSite> useSites = getUseSites(definition);
if (useSites.isEmpty()) {
return false;
}
for (UseSite site : useSites) {
// This catches the case where an object literal in goog.reflect.object
// and a prototype method have the same property name.
// NOTE(nicksantos): Maps and trogedit both do this by different
// mechanisms.
Node nameNode = site.node;
Collection<Definition> singleSiteDefinitions =
getDefinitionsReferencedAt(nameNode);
if (singleSiteDefinitions.size() > 1) {
return false;
}
Preconditions.checkState(!singleSiteDefinitions.isEmpty());
Preconditions.checkState(singleSiteDefinitions.contains(definition));
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
return true;
}
/**
* @return Whether the definition is directly exported.
*/
private boolean isExported(Definition definition) {
// Assume an exported method result is used.
Node lValue = definition.getLValue();
if (lValue == null) {
return true;
}
String partialName;
if (lValue.isGetProp()) {
partialName = lValue.getLastChild().getString();
} else if (lValue.isName()) {
partialName = lValue.getString();
} else {
// GETELEM is assumed to be an export or other expression are unknown
// uses.
return true;
}
CodingConvention codingConvention = compiler.getCodingConvention();
if (codingConvention.isExported(partialName)) {
return true;
}
return false;
}
/**
* @return Whether the function is defined in a non-aliasing expression.
*/
static boolean isSimpleFunctionDeclaration(Node fn) {
Node parent = fn.getParent();
Node gramps = parent.getParent();
// Simple definition finder doesn't provide useful results in some
// cases, specifically:
// - functions with recursive definitions
// - functions defined in object literals
// - functions defined in array literals
// Here we defined a set of known function declaration that are 'ok'.
// Some projects seem to actually define "JSCompiler_renameProperty"
// rather than simply having an extern definition. Don't mess with it.
Node nameNode = SimpleDefinitionFinder.getNameNodeFromFunctionNode(fn);
if (nameNode != null
&& nameNode.isName()) {
String name = nameNode.getString();
if (name.equals(NodeUtil.JSC_PROPERTY_NAME_FN) ||
name.equals(
ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) {
return false;
}
}
// example: function a(){};
if (NodeUtil.isFunctionDeclaration(fn)) {
return true;
}
// example: a = function(){};
// example: var a = function(){};
if (fn.getFirstChild().getString().isEmpty()
&& (NodeUtil.isExprAssign(gramps) || parent.isName())) {
return true;
}
return false;
}
/**
* @return the node defining the name for this function (if any).
*/
static Node getNameNodeFromFunctionNode(Node function) {
Preconditions.checkState(function.isFunction());
if (NodeUtil.isFunctionDeclaration(function)) {
return function.getFirstChild();
} else {
Node parent = function.getParent();
if (NodeUtil.isVarDeclaration(parent)) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (NodeUtil.isObjectLitKey(parent)) {
return parent;
}
}
return null;
}
/**
* Traverse a node and its children and remove any references to from
* the structures.
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
*/
void removeReferences(Node node) {
if (DefinitionsRemover.isDefinitionNode(node)) {
DefinitionSite defSite = definitionSiteMap.get(node);
if (defSite != null) {
Definition def = defSite.definition;
String name = getSimplifiedName(def.getLValue());
if (name != null) {
this.definitionSiteMap.remove(node);
this.nameDefinitionMultimap.remove(name, node);
}
}
} else {
Node useSite = node;
if (useSite.isGetProp()) {
String propName = useSite.getLastChild().getString();
if (propName.equals("apply") || propName.equals("call")) {
useSite = useSite.getFirstChild();
}
}
String name = getSimplifiedName(useSite);
if (name != null) {
this.nameUseSiteMultimap.remove(name, new UseSite(useSite, null, null));
}
}
for (Node child : node.children()) {
removeReferences(child);
}
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.
if (!block.hasChildren() && block.wasEmptyNode()) {
t.getCompiler().report(
t.makeError(block, SUSPICIOUS_SEMICOLON));
}
}
private void checkNaN(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LT:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
reportIfNaN(t, n.getFirstChild());
reportIfNaN(t, n.getLastChild());
}
}
private void reportIfNaN(NodeTraversal t, Node n) {
if (NodeUtil.isNaN(n)) {
t.getCompiler().report(
t.makeError(n.getParent(), SUSPICIOUS_COMPARISON_WITH_NAN));
}
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
!parent.isExprResult() &&
!parent.isScript()) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String getCode() throws IOException {
return getSourceFile().getCode();
}
/** Returns the module to which the input belongs. */
public JSModule getModule() {
return module;
}
/** Sets the module to which the input belongs. */
public void setModule(JSModule module) {
// An input may only belong to one module.
Preconditions.checkArgument(
module == null || this.module == null || this.module == module);
this.module = module;
}
/** Overrides the module to which the input belongs. */
void overrideModule(JSModule module) {
this.module = module;
}
public boolean isExtern() {
if (ast == null || ast.getSourceFile() == null) {
return false;
}
return ast.getSourceFile().isExtern();
}
void setIsExtern(boolean isExtern) {
if (ast == null || ast.getSourceFile() == null) {
return;
}
ast.getSourceFile().setIsExtern(isExtern);
}
public int getLineOffset(int lineno) {
return ast.getSourceFile().getLineOffset(lineno);
}
/** @return The number of lines in this input. */
public int getNumLines() {
return ast.getSourceFile().getNumLines();
}
@Override
public String toString() {
return getName();
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressible warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressible groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressibleGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
boolean visitedFunction = false;
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
if (type == Token.FUNCTION) {
info = NodeUtil.getBestJSDocInfo(current);
visitedFunction = true;
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
} else if (current.isVar() || current.isAssign()) {
// There's one edge case we're worried about:
// if the warning points to an assigment to a function, we
// want the suppressions on that function to apply.
// It's OK if we double-count some cases.
Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild());
if (rhs != null) {
if (rhs.isCast()) {
rhs = rhs.getFirstChild();
}
if (rhs.isFunction() && !visitedFunction) {
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
case Token.OBJECTLIT:
if (!mayHaveSideEffects(n)) {
return TernaryValue.TRUE;
}
break;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): regex literals as well.
switch (n.getType()) {
case Token.STRING:
case Token.STRING_KEY:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
return getStringValue(n.getDouble());
case Token.FALSE:
return "false";
case Token.TRUE:
return "true";
case Token.NULL:
return "null";
case Token.VOID:
return "undefined";
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? "false" : "true"; // reversed.
}
break;
case Token.ARRAYLIT:
return arrayToString(n);
case Token.OBJECTLIT:
return "[object Object]";
}
return null;
}
static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
/**
* When converting arrays to string using Array.prototype.toString or
* Array.prototype.join, the rules for conversion to String are different
* than converting each element individually. Specifically, "null" and
* "undefined" are converted to an empty string.
* @param n A node that is a member of an Array.
* @return The string representation.
*/
static String getArrayElementStringValue(Node n) {
return (NodeUtil.isNullOrUndefined(n) || n.isEmpty())
? "" : getStringValue(n);
}
static String arrayToString(Node literal) {
Node first = literal.getFirstChild();
StringBuilder result = new StringBuilder();
for (Node n = first; n != null; n = n.getNext()) {
String childValue = getArrayElementStringValue(n);
if (childValue == null) {
return null;
}
if (n != first) {
result.append(',');
}
result.append(childValue);
}
return result.toString();
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
case Token.NEG:
if (n.getChildCount() == 1 && n.getFirstChild().isName()
&& n.getFirstChild().getString().equals("Infinity")) {
return Double.NEGATIVE_INFINITY;
}
return null;
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? 0.0 : 1.0; // reversed.
}
break;
case Token.STRING:
return getStringNumberValue(n.getString());
case Token.ARRAYLIT:
case Token.OBJECTLIT:
String value = getStringValue(n);
return value != null ? getStringNumberValue(value) : null;
}
return null;
}
static Double getStringNumberValue(String rawJsString) {
if (rawJsString.contains("\u000b")) {
// vertical tab is not always whitespace
return null;
}
String s = trimJsWhiteSpace(rawJsString);
// return ScriptRuntime.toNumber(s);
if (s.length() == 0) {
return 0.0;
}
if (s.length() > 2
&& s.charAt(0) == '0'
&& (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
// Attempt to convert hex numbers.
try {
return Double.valueOf(Integer.parseInt(s.substring(2), 16));
} catch (NumberFormatException e) {
return Double.NaN;
}
}
if (s.length() > 3
&& (s.charAt(0) == '-' || s.charAt(0) == '+')
&& s.charAt(1) == '0'
&& (s.charAt(2) == 'x' || s.charAt(2) == 'X')) {
// hex numbers with explicit signs vary between browsers.
return null;
}
// Firefox and IE treat the "Infinity
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>" differently. Firefox is case
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
if (s.equals("infinity")
|| s.equals("-infinity")
|| s.equals("+infinity")) {
return null;
}
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return Double.NaN;
}
}
static String trimJsWhiteSpace(String s) {
int start = 0;
int end = s.length();
while (end > 0
&& isStrWhiteSpaceChar(s.charAt(end - 1)) == TernaryValue.TRUE) {
end--;
}
while (start < end
&& isStrWhiteSpaceChar(s.charAt(start)) == TernaryValue.TRUE) {
start++;
}
return s.substring(start, end);
}
/**
* Copied from Rhino's ScriptRuntime
*/
public static TernaryValue isStrWhiteSpaceChar(int c) {
switch (c) {
case '\u000B': // <VT>
return TernaryValue.UNKNOWN; // IE says "no", ECMAScript says "yes"
case ' ': // <SP>
case '\n': // <LF>
case '\r': // <CR>
case '\t': // <TAB>
case '\u00A0': // <NBSP>
case '\u000C': // <FF>
case '\u2028': // <LS>
case '\u2029': // <PS>
case '\uFEFF': // <BOM>
return TernaryValue.TRUE;
default:
return (Character.getType(c) == Character.SPACE_SEPARATOR)
? TernaryValue.TRUE : TernaryValue.FALSE;
}
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Preconditions.checkState(n.isFunction());
Node parent = n.getParent();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getQualifiedName();
case Token.ASSIGN:
// qualified.name = function() ...
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
String name = n.getFirstChild().getQualifiedName();
return name;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
public static String getNearestFunctionName(Node n) {
if (!n.isFunction()) {
return null;
}
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.CAST:
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
*/
static boolean isRelationalOperation(Node n) {
switch (n.getType()) {
case Token.GT: // equal
case Token.GE: // not equal
case Token.LT: // exactly equal
case Token.LE: // exactly not equal
return true;
}
return false;
}
/**
* Returns the inverse of an operator if it is invertible.
* ex. '>' ==> '<'
*/
static int getInverseOperator(int type) {
switch (type) {
case Token.GT:
return Token.LT;
case Token.LT:
return Token.GT;
case Token.GE:
return Token.LE;
case Token.LE:
return Token.GE;
}
return Token.ERROR;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured variables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.CAST:
return isLiteralValue(n.getFirstChild(), includeFunctions);
case Token.ARRAYLIT:
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
static Node newExpr(Node child) {
return IR.exprResult(child).srcref(child);
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.CAST:
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.PARAM_LIST:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.STRING_KEY:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> valid parse tree.
return n.isName() && n.getParent().isVar();
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
if (parent.isVar()) {
return n.getFirstChild();
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.isExprResult()
&& n.getFirstChild().isAssign();
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.isExprResult()
&& n.getFirstChild().isCall();
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.isFor()
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (parent.isFunction()) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT_CASE:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT_CASE:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.isScript() || n.isBlock();
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.isCase() || n.isDefaultCase();
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return n.isName() && !n.getString().isEmpty();
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.isTry() && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Whether the node is a CATCH container BLOCK. */
static boolean isTryCatchNodeContainer(Node n) {
Node parent = n.getParent();
return parent.isTry()
&& parent.getFirstChild().getNext() == n;
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
if (isTryFinallyNode(parent, node)) {
if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) {
// A finally can only be removed if there is a catch.
parent.removeChild(node);
} else {
// Otherwise, only its children can be removed.
node.detachChildren();
}
} else if (node.isCatch()) {
// The CATCH can can only be removed if there is a finally clause.
Node tryNode = node.getParent().getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachFromParent();
} else if (isTryCatchNodeContainer(node)) {
// The container node itself can't be removed, but the contained CATCH
// can if there is a 'finally' clause
Node tryNode = node.getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachChildren();
} else if (node.isBlock()) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (isStatementBlock(parent)
|| isSwitchCase(node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.isVar()) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (parent.isLabel()
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.isFor()
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, IR.empty());
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of " + parent.toString());
}
}
/**
* Add a finally block if one does not exist.
*/
static void maybeAddFinally(Node tryNode) {
Preconditions.checkState(tryNode.isTry());
if (!NodeUtil.hasFinally(tryNode)) {
tryNode.addChildrenToBack(IR.block().srcref(tryNode));
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.isBlock());
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* @param node A node
* @return Whether the call is a NEW or CALL node.
*/
static boolean isCallOrNew(Node node) {
return node.isCall() || node.isNew();
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(fn.isFunction());
return fn.getLastChild();
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.isFunction() && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().isScript()
|| n.getParent().getParent().isFunction());
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.isFunction() && !isStatement(n);
}
/**
* Returns whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
static boolean isBleedingFunctionName(Node n) {
return n.isName() && !n.getString().isEmpty() &&
isFunctionExpression(n.getParent());
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock(node.getLastChild());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
// TODO(johnlenz): rename this function
Preconditions.checkArgument(function.isFunction());
return isNameReferenced(
function.getLastChild(),
"arguments",
MATCH_NOT_FUNCTION);
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.isCall()) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.isString()) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> * Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isVarOrSimpleAssignLhs(Node n, Node parent) {
return (parent.isAssign() && parent.getFirstChild() == n) ||
parent.isVar();
}
/**
* Determines whether this node is used as an L-value. Notice that sometimes
* names are used as both L-values and R-values.
*
* We treat "var x;" as a pseudo-L-value, which kind of makes sense if you
* treat it as "assignment to 'undefined' at the top of the scope". But if
* we're honest with ourselves, it doesn't make sense, and we only do this
* because it makes sense to treat this as syntactically similar to
* "var x = 0;".
*
* @param n The node
* @return True if n is an L-value.
*/
public static boolean isLValue(Node n) {
Preconditions.checkArgument(n.isName() || n.isGetProp() ||
n.isGetElem());
Node parent = n.getParent();
if (parent == null) {
return false;
}
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == n)
|| parent.isVar()
|| (parent.isFunction() && parent.getFirstChild() == n)
|| parent.isDec()
|| parent.isInc()
|| parent.isParamList()
|| parent.isCatch();
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
*/
static boolean isObjectLitKey(Node node) {
switch (node.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key,
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.isBlock() ||
addingRoot.isScript());
Preconditions.checkState(addingRoot.getFirstChild() == null ||
!addingRoot.getFirstChild().isScript());
return addingRoot;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name);
}
Node node;
String nodeName = name.substring(0, endPos);
if ("this".equals(nodeName)) {
node = IR.thisNode();
} else {
node = newName(convention, nodeName);
}
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = IR.string(part);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = IR.getprop(node, propNode);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNodeDeclaration(
CodingConvention convention, String name, Node value, JSDocInfo info) {
Node result;
Node nameNode = newQualifiedNameNode(convention, name);
if (nameNode.isName()) {
result = IR.var(nameNode, value);
result.setJSDocInfo(info);
} else {
result = IR.exprResult(IR.assign(nameNode, value));
result.getFirstChild().setJSDocInfo(info);
}
return result;
}
/**
* Creates a node representing a qualified name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param basis
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>NAME_PROP, originalName);
return nameNode;
}
/** Test if all characters in the string are in the Basic Latin (aka ASCII)
* character set - that they have UTF-16 values equal to or below 0x7f.
* This check can find which identifiers with Unicode characters need to be
* escaped in order to allow resulting files to be processed by non-Unicode
* aware UNIX tools and editors.
* *
* See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
* for more on Basic Latin.
*
* @param s The string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name is a valid variable name.
*/
static boolean isValidSimpleName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, Unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
/**
* Determines whether the given name is a valid qualified name.
*/
// TODO(nicksantos): This should be moved into a "Language" API,
// so that the results are different for es5 and es3.
public static boolean isValidQualifiedName(String name) {
if (name.endsWith(".") || name.startsWith(".")) {
return false;
}
String[] parts = name.split("\\.");
for (String part : parts) {
if (!isValidSimpleName(part)) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return isValidSimpleName(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
@Override
public void visit(Node n) {
if (n.isName()) {
Node parent = n.getParent();
if (parent != null && parent.isVar()) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
@Override
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.isVar();
}
}
/**
* A predicate for matching anything except function nodes.
*/
private static class MatchNotFunction implements Predicate<Node>{
@Override
public boolean apply(Node n) {
return !n.isFunction();
}
}
static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction();
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
@Override
public boolean apply(Node n) {
Node parent = n.getParent();
return n.isBlock()
|| (!n.isFunction() && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (has(c, pred, traverseChildrenPred)) {
return true;
}
}
return false;
}
/**
* @return The number of times the the predicate is true for the node
* or any of its children.
*/
static int getCount(
Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) {
int total = 0;
if (pred.
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> node represents a constant variable
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
if (parent.isGetProp() && node == parent.getLastChild()) {
return convention.isConstantKey(node.getString());
} else if (isObjectLitKey(node)) {
return convention.isConstantKey(node.getString());
} else if (node.isName()) {
return convention.isConstant(node.getString());
}
return false;
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getSourceFileName();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static StaticSourceFile getSourceFile(Node n) {
StaticSourceFile sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getStaticSourceFile();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The InputId property on the node or its ancestors.
*/
public static InputId getInputId(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return (n != null && n.isScript()) ? n.getInputId() : null;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = !isGet(callTarget);
Node call = IR.call(callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(function.isFunction());
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
/**
* Returns whether this is a target of a call or new.
*/
static boolean isCallOrNewTarget(Node target) {
Node parent = target.getParent();
return parent != null
&& NodeUtil.isCallOrNew(parent)
&& parent.getFirstChild() == target;
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return propNode.isString() && "toString".equals(propNode.getString());
}
return false;
}
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (parent.isName()) {
return getBestJSDocInfo(parent);
} else if (parent.isAssign()) {
return getBestJSDocInfo(parent);
} else if (isObjectLitKey(parent)) {
return parent.getJSDocInfo();
} else if (parent.isFunction()) {
return parent.getJSDocInfo();
} else if (parent.isVar() && parent.hasOneChild()) {
return parent.getJSDocInfo();
} else if ((parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestJSDocInfo(parent);
} else if (parent
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.isCast()) {
return parent.getJSDocInfo();
}
}
return info;
}
/** Find the l-value that the given r-value is being assigned to. */
static Node getBestLValue(Node n) {
Node parent = n.getParent();
boolean isFunctionDeclaration = isFunctionDeclaration(n);
if (isFunctionDeclaration) {
return n.getFirstChild();
} else if (parent.isName()) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (isObjectLitKey(parent)) {
return parent;
} else if (
(parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestLValue(parent);
} else if (parent.isCast()) {
return getBestLValue(parent);
}
return null;
}
/** Gets the r-value of a node returned by getBestLValue. */
static Node getRValueOfLValue(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.ASSIGN:
return n.getNext();
case Token.VAR:
return n.getFirstChild();
case Token.FUNCTION:
return parent;
}
return null;
}
/** Get the owner of the given l-value node. */
static Node getBestLValueOwner(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue)) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue)) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.EXPR_RESULT:
return false;
case Token.CAST:
return isExpressionResultUsed(parent);
case Token.HOOK:
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
case Token.AND:
case Token.OR:
return (expr == parent.getFirstChild())
? true : isExpressionResultUsed(parent);
case Token.COMMA:
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval. This we pretend that this is "used".
if (expr == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
expr.getNext().isName() &&
"eval".equals(expr.getNext().getString())) {
return true;
}
}
return (expr == parent.getFirstChild())
? false : isExpressionResultUsed(parent);
case Token.FOR:
if (!NodeUtil.isForIn(parent)) {
// Only an expression whose result is in the condition part of the
// expression is used.
return (parent.getChildAtIndex(1) == expr);
}
break;
}
return true;
}
/**
* @param n The expression to check.
* @return Whether the expression is unconditionally executed only once in the
* containing execution scope.
*/
static boolean isExecutedExactlyOnce(Node n) {
inspect: do {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.IF:
case Token.HOOK:
case Token.AND:
case Token.OR:
if (parent.getFirstChild() != n) {
return false;
}
// other ancestors may be conditional
continue inspect;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
if (parent.getChildAtIndex(1) != n) {
return false;
}
} else {
if (parent.getFirstChild() != n) {
return false;
}
}
// other ancestors may be conditional
continue inspect;
case Token.WHILE:
case Token.DO:
return false;
case Token.TRY:
// Consider all code under a try/catch to be conditionally executed.
if (!hasFinally(parent) || parent.getLastChild() != n) {
return false;
}
continue inspect;
case Token.CASE:
case Token.DEFAULT_CASE:
return false;
case Token.SCRIPT:
case Token.FUNCTION:
// Done, we've reached the scope root.
break inspect;
}
} while ((n = n.getParent()) != null);
return true;
}
/**
* @return An appropriate AST node for the boolean value.
*/
static Node booleanNode(boolean value) {
return value ? IR.trueNode() : IR.falseNode();
}
/**
*
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.getFirstChild().getDouble());
} else {
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type), Context.OTHER);
}
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), 1, Context.OTHER);
cc.addOp(":", true);
addExpr(last, 1, Context.OTHER);
break;
}
case Token.REGEXP:
if (!first.isString() ||
!last.isString()) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.FUNCTION:
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Preconditions.checkState(childCount == 3);
boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
Preconditions.checkState(n.getParent().isObjectLit());
Preconditions.checkState(childCount == 1);
Preconditions.checkState(first.isFunction());
// Get methods are unnamed
Preconditions.checkState(first.getFirstChild().getString().isEmpty());
if (type == Token.GETTER_DEF) {
// Get methods have no parameters.
Preconditions.checkState(!first.getChildAtIndex(1).hasChildren());
add("get ");
} else {
// Set methods have one parameter.
Preconditions.checkState(first.getChildAtIndex(1).hasOneChild());
add("set ");
}
// The name is on the GET or SET node.
String name = n.getString();
Node fn = first;
Node parameters = fn.getChildAtIndex(1);
Node body = fn.getLastChild();
// Add the property name.
if (!n.isQuotedString() &&
TokenStream.isJSIdentifier(name) &&
// do not encode literally any non-literal characters that were
// Unicode escaped.
NodeUtil.isLatin(name)) {
add(name);
} else {
// Determine if the string is a
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> simple number.
double d = getSimpleNumber(name);
if (!Double.isNaN(d)) {
cc.addNumber(d);
} else {
addJsString(n);
}
}
add(parameters);
add(body, Context.PRESERVE_BLOCK);
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().isScript());
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.isVar()) {
cc.endStatement();
}
if (c.isFunction()) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top-level statements are more homogeneous.
if (preferLineBreaks) {
cc.notePreferredLineBreak();
}
}
if (preserveBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.isVar()) {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
}
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyStatement(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP:
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>(")");
break;
default:
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* We could use addList recursively here, but sometimes we produce
* very deeply nested operators and run out of stack space, so we
* just unroll the recursion when possible.
*
* We assume nodes are left-recursive.
*/
private void unrollBinaryOperator(
Node n, int op, String opStr, Context context,
Context rhsContext, int leftPrecedence, int rightPrecedence) {
Node firstNonOperator = n.getFirstChild();
while (firstNonOperator.getType() == op) {
firstNonOperator = firstNonOperator.getFirstChild();
}
addExpr(firstNonOperator, leftPrecedence, context);
Node current = firstNonOperator;
do {
current = current.getParent();
cc.addOp(opStr, true);
addExpr(current.getFirstChild().getNext(), rightPrecedence, rhsContext);
} while (current != n);
}
static boolean isSimpleNumber(String s) {
int len = s.length();
if (len == 0) {
return false;
}
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c < '0' || c > '9') {
return false;
}
}
return len == 1 || s.charAt(0) != '0';
}
static double getSimpleNumber(String s) {
if (isSimpleNumber(s)) {
try {
long l = Long.parseLong(s);
if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) {
return l;
}
} catch (NumberFormatException e) {
// The number was too long to parse. Fall through to NaN.
}
}
return Double.NaN;
}
/**
* @return Whether the name is an indirect eval.
*/
private boolean isIndirectEval(Node n) {
return n.isName() && "eval".equals(n.getString()) &&
!n.getBooleanProp(Node.DIRECT_EVAL);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyStatement(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && !n.isBlock()) {
throw new Error("Missing BLOCK child.");
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if (n.isBlock()) {
int count = getNonEmptyChildCount
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>ARATIVELY_UNBOUND_VARS_WITHOUT_TYPES =
new Predicate<Var>() {
@Override public boolean apply(Var var) {
return var.getParentNode() != null &&
var.getType() == null && // no declared type
var.getParentNode().isVar() &&
!var.isExtern();
}
};
/** Stores info about a variable */
public static class Var
implements StaticSlot<JSType>, StaticReference<JSType> {
/** name */
final String name;
/** Var node */
final Node nameNode;
/**
* The variable's type.
*/
private JSType type;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
final CompilerInput input;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
final int index;
/** The enclosing scope */
final Scope scope;
/** @see #isMarkedEscaped */
private boolean markedEscaped = false;
/** @see #isMarkedAssignedExactlyOnce */
private boolean markedAssignedExactlyOnce = false;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred, String name, Node nameNode, JSType type,
Scope scope, int index, CompilerInput input) {
this.name = name;
this.nameNode = nameNode;
this.type = type;
this.scope = scope;
this.index = index;
this.input = input;
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
@Override
public String getName() {
return name;
}
/**
* Gets the node for the name of the variable.
*/
@Override
public Node getNode() {
return nameNode;
}
CompilerInput getInput() {
return input;
}
@Override
public StaticSourceFile getSourceFile() {
return nameNode.getStaticSourceFile();
}
@Override
public Var getSymbol() {
return this;
}
@Override
public Var getDeclaration() {
return nameNode == null ? null : this;
}
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode());
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> 0;
}
static Scope createGlobalScope(Node rootNode) {
return new Scope(rootNode, false);
}
static Scope createLatticeBottom(Node rootNode) {
return new Scope(rootNode, true);
}
/** The depth of the scope. The global scope has depth 0. */
int getDepth() {
return depth;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
@Override
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
@Override
public JSType getTypeOfThis() {
if (isGlobal()) {
return ObjectType.cast(rootNode.getJSType());
}
Preconditions.checkState(rootNode.isFunction());
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType.isFunctionType()) {
return nodeType.toMaybeFunctionType().getTypeOfThis();
} else {
return parent.getTypeOfThis();
}
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input, true);
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
* @param inferred Whether this variable's type is inferred (as opposed
* to declared).
*/
Var declare(String name, Node nameNode,
JSType type, CompilerInput input, boolean inferred) {
Preconditions.checkState(name != null && name.length() > 0);
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input);
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>Calls(
List<TweakFunctionCall> calls) {
for (TweakFunctionCall call : calls) {
Node callNode = call.callNode;
Node objNode = createCompilerDefaultValueOverridesVarNode(callNode);
callNode.getParent().replaceChild(callNode, objNode);
}
return !calls.isEmpty();
}
/**
* Removes all CALL nodes in the given TweakInfos, replacing calls to getter
* functions with the tweak's default value.
*/
private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) {
for (TweakInfo tweakInfo : tweakInfos.values()) {
boolean isRegistered = tweakInfo.isRegistered();
for (TweakFunctionCall functionCall : tweakInfo.functionCalls) {
Node callNode = functionCall.callNode;
Node parent = callNode.getParent();
if (functionCall.tweakFunc.isGetterFunction()) {
Node newValue;
if (isRegistered) {
newValue = tweakInfo.getDefaultValueNode().cloneNode();
} else {
// When we find a getter of an unregistered tweak, there has
// already been a warning about it, so now just use a default
// value when stripping.
TweakFunction registerFunction =
functionCall.tweakFunc.registerFunction;
newValue = registerFunction.createDefaultValueNode();
}
parent.replaceChild(callNode, newValue);
} else {
Node voidZeroNode = IR.voidNode(IR.number(0).srcref(callNode))
.srcref(callNode);
parent.replaceChild(callNode, voidZeroNode);
}
}
}
return !tweakInfos.isEmpty();
}
/**
* Creates a JS object that holds a map of tweakId -> default value override.
*/
private Node createCompilerDefaultValueOverridesVarNode(
Node sourceInformationNode) {
Node objNode = IR.objectlit().srcref(sourceInformationNode);
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
Node objKeyNode = IR.stringKey(entry.getKey())
.copyInformationFrom(sourceInformationNode);
Node objValueNode = entry.getValue().cloneNode()
.copyInformationFrom(sourceInformationNode);
objKeyNode.addChildToBack(objValueNode);
objNode.addChildToBack(objKeyNode);
}
return objNode;
}
/** Sets the default values of tweaks based on compiler options. */
private void applyCompilerDefaultValueOverrides(
Map<String, TweakInfo> tweakInfos) {
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
String tweakId = entry.getKey();
TweakInfo tweakInfo = tweakInfos.get(tweakId);
if (tweakInfo == null) {
compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
} else {
TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
Node value = entry.getValue();
if (!
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> root);
}
// Find prototype properties that will affect our analysis.
Preconditions.checkState(namespace.hasExternsRoot());
findPrototypeProps("Object", objectPrototypeProps);
findPrototypeProps("Function", functionPrototypeProps);
objectPrototypeProps.addAll(
convention.getIndirectlyDeclaredProperties());
for (Name name : namespace.getNameForest()) {
// Skip extern names. Externs are often not runnable as real code,
// and will do things like:
// var x;
// x.method;
// which this check forbids.
if (name.inExterns) {
continue;
}
checkDescendantNames(name, name.globalSets + name.localSets > 0);
}
}
private void findPrototypeProps(String type, Set<String> props) {
Name slot = namespace.getSlot(type);
if (slot != null) {
for (Ref ref : slot.getRefs()) {
if (ref.type == Ref.Type.PROTOTYPE_GET) {
Node fullName = ref.getNode().getParent().getParent();
if (fullName.isGetProp()) {
props.add(fullName.getLastChild().getString());
}
}
}
}
}
/**
* Checks to make sure all the descendants of a name are defined if they
* are referenced.
*
* @param name A global name.
* @param nameIsDefined If true, {@code name} is defined. Otherwise, it's
* undefined, and any references to descendant names should emit warnings.
*/
private void checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.getDeclaration();
Name parent = name.parent;
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.getRefs()) {
// Don't worry about global exprs.
boolean isGlobalExpr = ref.getNode().getParent().isExprResult();
if (!isDefined && !isTypedef(ref)) {
if (!isGlobalExpr) {
reportRefToUndefinedName(name, ref);
}
} else
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> if (declaration != null &&
ref.getModule() != declaration.getModule() &&
!moduleGraph.dependsOn(
ref.getModule(), declaration.getModule())) {
reportBadModuleReference(name, ref);
} else {
// Check for late references.
if (ref.scope.isGlobal()) {
// Prototype references are special, because in our reference graph,
// A.prototype counts as a reference to A.
boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET);
Name owner = isPrototypeGet ? name : parent;
boolean singleGlobalParentDecl =
owner != null &&
owner.getDeclaration() != null &&
owner.localSets == 0;
if (singleGlobalParentDecl &&
owner.getDeclaration().preOrderIndex > ref.preOrderIndex) {
String refName = isPrototypeGet
? name.getFullName() + ".prototype"
: name.getFullName();
compiler.report(
JSError.make(ref.source.getName(), ref.node,
NAME_DEFINED_LATE_WARNING,
refName,
owner.getFullName(),
owner.getDeclaration().source.getName(),
String.valueOf(owner.getDeclaration().node.getLineno())));
}
}
}
}
}
private boolean isTypedef(Ref ref) {
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.isExprResult()) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return true;
}
}
return false;
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.source.getName(), ref.node, STRICT_MODULE_DEP_QNAME,
ref.getModule().getName(),
name.getDeclaration().getModule().getName(),
name.getFullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
compiler.report(
JSError.make(ref.getSourceName(), ref.node, level,
UNDEFINED_NAME_WARNING, name.getFullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private boolean propertyMustBeInitializedByFullName(Name name) {
// If an object or function literal in the global namespace is never
// aliased, then its properties can only come from one of 2 places:
// 1) From its prototype chain, or
// 2) From an assignment to its fully qualified name.
// If we assume #1 is not the case, then #2 implies
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> that its
// properties must all be modeled in the GlobalNamespace as well.
//
// We assume that for global object literals and types (constructors and
// interfaces), we can find all the properties inherited from the prototype
// chain of functions and objects.
if (name.parent == null) {
return false;
}
boolean parentIsAliased = false;
if (name.parent.aliasingGets > 0) {
for (Ref ref : name.parent.getRefs()) {
if (ref.type == Ref.Type.ALIASING_GET) {
Node aliaser = ref.getNode().getParent();
// We don't need to worry about known aliased, because
// they're already covered by the getIndirectlyDeclaredProperties
// call at the top.
boolean isKnownAlias =
aliaser.isCall() &&
(convention.getClassesDefinedByCall(aliaser) != null ||
convention.getSingletonGetterClassName(aliaser) != null);
if (!isKnownAlias) {
parentIsAliased = true;
}
}
}
}
if (parentIsAliased) {
return false;
}
if (objectPrototypeProps.contains(name.getBaseName())) {
return false;
}
if (name.parent.type == Name.Type.OBJECTLIT) {
return true;
}
if (name.parent.type == Name.Type.FUNCTION &&
name.parent.isDeclaredType() &&
!functionPrototypeProps.contains(name.getBaseName())) {
return true;
}
return false;
}
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.intValue = intValue;
}
@Override
public int getIntValue() {
return intValue;
}
@Override
public Object getObjectValue() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return String.valueOf(intValue);
}
@Override
public PropListItem chain(PropListItem next) {
return new IntPropListItem(getType(), intValue, next);
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = right;
mid.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(mid2.parent == null);
Preconditions.checkArgument(mid2.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = mid2;
mid.parent = this;
mid2.next = right;
mid2.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, int lineno, int charno) {
type = nodeType;
parent = null;
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node child, int lineno, int charno) {
this(nodeType, child);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node right, int lineno, int charno) {
this(nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new String
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>Node(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public int getIndexOfChild(Node child) {
Node n = first;
int i = 0;
while (n != null) {
if (child == n) {
return i;
}
n = n.next;
i++;
}
return -1;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = null;
if (last == null) {
first = last = child;
return;
}
last.next = child;
last = child;
}
public void addChildrenToFront(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSib = children.getLastSibling();
lastSib.next = first;
first = children;
if (last == null) {
last = lastSib
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>;
}
}
public void addChildrenToBack(Node children) {
addChildrenAfter(children, getLastChild());
}
/**
* Add 'child' before 'node'.
*/
public void addChildBefore(Node newChild, Node node) {
Preconditions.checkArgument(node != null && node.parent == this,
"The existing child node of the parent should not be null.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
if (first == node) {
newChild.parent = this;
newChild.next = first;
first = newChild;
return;
}
Node prev = getChildBefore(node);
addChildAfter(newChild, prev);
}
/**
* Add 'child' after 'node'.
*/
public void addChildAfter(Node newChild, Node node) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
addChildrenAfter(newChild, node);
}
/**
* Add all children after 'node'.
*/
public void addChildrenAfter(Node children, Node node) {
Preconditions.checkArgument(node == null || node.parent == this);
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSibling = children.getLastSibling();
if (node != null) {
Node oldNext = node.next;
node.next = children;
lastSibling.next = oldNext;
if (node == last) {
last = lastSibling;
}
} else {
// Append to the beginning.
if (first != null) {
lastSibling.next = first;
} else {
last = lastSibling;
}
first = children;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null) {
first = first.next;
} else {
prev.next = child.next;
}
if (child == last) {
last = prev;
}
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last) {
last = newChild;
}
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last) {
last = newChild;
}
child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.getType()) {
x = x.getNext();
}
return x;
}
/**
* Clone the properties from the provided node without copying
* the property object. The receiving node may not have any
* existing properties.
* @param other The node to clone properties from.
* @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
* @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.getType() == propType) {
return item.getNext();
} else {
PropListItem result = removeProp(item.getNext(), propType);
if (result != item.getNext()) {
return item.chain(result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.getObjectValue();
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> /** Returns the source file associated with this input. May be null */
public StaticSourceFile getStaticSourceFile() {
return ((StaticSourceFile) this.getProp(STATIC_SOURCE_FILE));
}
/**
* @param inputId
*/
public void setInputId(InputId inputId) {
this.putProp(INPUT_ID, inputId);
}
/**
* @return The Id of the CompilerInput associated with this Node.
*/
public InputId getInputId() {
return ((InputId) this.getProp(INPUT_ID));
}
public boolean isFromExterns() {
StaticSourceFile file = getStaticSourceFile();
return file == null ? false : file.isExtern();
}
public int getLength() {
return getIntProp(LENGTH);
}
public void setLength(int length) {
putIntProp(LENGTH, length);
}
public int getLineno() {
return extractLineno(sourcePosition);
}
public int getCharno() {
return extractCharno(sourcePosition);
}
public int getSourceOffset() {
StaticSourceFile file = getStaticSourceFile();
if (file == null) {
return -1;
}
int lineno = getLineno();
if (lineno == -1) {
return -1;
}
return file.getLineOffset(lineno) + getCharno();
}
public int getSourcePosition() {
return sourcePosition;
}
public void setLineno(int lineno) {
int charno = getCharno();
if (charno == -1) {
charno = 0;
}
sourcePosition = mergeLineCharNo(lineno, charno);
}
public void setCharno(int charno) {
sourcePosition = mergeLineCharNo(getLineno(), charno);
}
public void setSourceEncodedPosition(int sourcePosition) {
this.sourcePosition = sourcePosition;
}
public void setSourceEncodedPositionForTree(int sourcePosition) {
this.sourcePosition = sourcePosition;
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.setSourceEncodedPositionForTree(sourcePosition);
}
}
/**
* Merges the line number and character number in one integer. The Character
* number takes the first 12 bits and the line number takes the rest. If
* the character number is greater than <code>2<sup>12</sup>-1</code> it is
* adjusted to <code>2<sup>12</sup>-1</code>.
*/
protected static int mergeLineCharNo(int lineno, int charno) {
if (lineno < 0 || charno < 0) {
return -1;
} else if ((charno & ~COLUMN_MASK) != 0) {
return lineno << COLUMN_BITS | COLUMN_MASK;
} else {
return lineno << COLUMN_BITS | (charno & COLUMN_MASK);
}
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> {
current = current.getNext();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
// ==========================================================================
// Accessors
PropListItem getPropListHeadForTesting() {
return propListHead;
}
public Node getParent() {
return parent;
}
/**
* Gets the ancestor node relative to this.
*
* @param level 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while (node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
@Override
public Iterator<Node> iterator() {
return new Iterator<Node>() {
@Override
public boolean hasNext() {
return cur != null;
}
@Override
public Node next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Node n = cur;
cur = cur.getParent();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next) {
c++;
}
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) {
last = prev;
}
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> = (Node) super.clone();
// PropListItem lists are immutable and can be shared so there is no
// need to clone them here.
result.next = null;
result.first = null;
result.last = null;
result.parent = null;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
/**
* @return A detached clone of the Node and all its children.
*/
public Node cloneTree() {
Node result = cloneNode();
for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) {
Node n2clone = n2.cloneTree();
n2clone.parent = result;
if (result.last != null) {
result.last.next = n2clone;
}
if (result.first == null) {
result.first = n2clone;
}
result.last = n2clone;
}
return result;
}
/**
* Copies source file and name information from the other
* node given to the current node. Used for maintaining
* debug information across node append and remove operations.
* @return this
*/
// TODO(nicksantos): The semantics of this method are ill-defined. Delete it.
public Node copyInformationFrom(Node other) {
if (getProp(ORIGINALNAME_PROP) == null) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
}
if (getProp(STATIC_SOURCE_FILE) == null) {
putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE));
sourcePosition = other.sourcePosition;
}
return this;
}
/**
* Copies source file and name information from the other node to the
* entire tree rooted at this node.
* @return this
*/
// TODO(nicksantos): The semantics of this method are ill-defined. Delete it.
public Node copyInformationFromForTree(Node other) {
copyInformationFrom(other);
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.copyInformationFromForTree(other);
}
return this;
}
/**
* Overwrite all the source information in this node with
* that of {@code other}.
*/
public Node useSourceInfoFrom(Node other) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE));
sourcePosition = other.sourcePosition;
return this;
}
public Node srcref(Node other) {
return useSourceInfoFrom(other);
}
/**
* Overwrite all the source information in this node and its subtree with
* that of {@code other}.
*/
public Node useSourceInfoFromForTree(Node other)
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> {
useSourceInfoFrom(other);
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.useSourceInfoFromForTree(other);
}
return this;
}
public Node srcrefTree(Node other) {
return useSourceInfoFromForTree(other);
}
/**
* Overwrite all the source information in this node with
* that of {@code other} iff the source info is missing.
*/
public Node useSourceInfoIfMissingFrom(Node other) {
if (getProp(ORIGINALNAME_PROP) == null) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
}
if (getProp(STATIC_SOURCE_FILE) == null) {
putProp(STATIC_SOURCE_FILE, other.getProp(STATIC_SOURCE_FILE));
sourcePosition = other.sourcePosition;
}
return this;
}
/**
* Overwrite all the source information in this node and its subtree with
* that of {@code other} iff the source info is missing.
*/
public Node useSourceInfoIfMissingFromForTree(Node other) {
useSourceInfoIfMissingFrom(other);
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.useSourceInfoIfMissingFromForTree(other);
}
return this;
}
//==========================================================================
// Custom annotations
public JSType getJSType() {
return jsType;
}
public void setJSType(JSType jsType) {
this.jsType = jsType;
}
public FileLevelJsDocBuilder getJsDocBuilderForNode() {
return new FileLevelJsDocBuilder();
}
/**
* An inner class that provides back-door access to the license
* property of the JSDocInfo property for this node. This is only
* meant to be used for top-level script nodes where the
* {@link com.google.javascript.jscomp.parsing.JsDocInfoParser} needs to
* be able to append directly to the top-level node, not just the
* current node.
*/
public class FileLevelJsDocBuilder {
public void append(String fileLevelComment) {
JSDocInfo jsDocInfo = getJSDocInfo();
if (jsDocInfo == null) {
// TODO(user): Is there a way to determine whether to
// parse the JsDoc documentation from here?
jsDocInfo = new JSDocInfo(false);
}
String license = jsDocInfo.getLicense();
if (license == null) {
license = "";
}
jsDocInfo.setLicense(license + fileLevelComment);
setJSDocInfo(jsDocInfo);
}
}
/**
* Get the {@link JSDocInfo} attached to this node.
* @return the information or {@code null} if no JSDoc is attached to this
* node
*/
public
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> connectToPossibleExceptionHandler(iter, iter);
} else {
// We have for (item in collection) { body }
Node item = forNode.getFirstChild();
Node collection = item.getNext();
Node body = collection.getNext();
// The collection behaves like init.
createEdge(collection, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
connectToPossibleExceptionHandler(forNode, collection);
}
}
private void handleSwitch(Node node) {
// Transfer to the first non-DEFAULT CASE. if there are none, transfer
// to the DEFAULT or the EMPTY node.
Node next = getNextSiblingOfType(
node.getFirstChild().getNext(), Token.CASE, Token.EMPTY);
if (next != null) { // Has at least one CASE or EMPTY
createEdge(node, Branch.UNCOND, next);
} else { // Has no CASE but possibly a DEFAULT
if (node.getFirstChild().getNext() != null) {
createEdge(node, Branch.UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.isCase());
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT_CASE);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.isBlock() && parent != null &&
parent.isTry() &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.isFunction()) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT_CASE:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.isBlock() && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
* discover a FINALLY, we will connect the BREAK to that FINALLY. From then
* on, we will just record the control
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
if (parent == null) {
if (compiler.isIdeMode()) {
// In IDE mode, we expect that the data flow graph may
// not be well-formed.
return;
} else {
throw new IllegalStateException("Cannot find break target.");
}
}
previous = cur;
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this));
} else {
finallyMap.put(lastJump, computeFollowNode(cur, this));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find continue target.");
previous = cur;
}
Node iter = cur;
if (cur.getChildCount() == 4) {
iter = cur.getFirstChild().getNext().getNext();
}
if (lastJump == node) {
createEdge(node, Branch.UNCOND, iter);
} else {
finallyMap.put(lastJump, iter);
}
}
private void handleReturn(Node node) {
Node lastJump = null;
for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) {
Node curHandler = iter.next();
if (curHandler.isFunction()) {
break;
}
if (NodeUtil
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.hasFinally(curHandler)) {
if (lastJump == null) {
createEdge(node, Branch.UNCOND, curHandler.getLastChild());
} else {
finallyMap.put(lastJump,
computeFallThrough(curHandler.getLastChild()));
}
lastJump = curHandler;
}
}
if (node.hasChildren()) {
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
if (lastJump == null) {
createEdge(node, Branch.UNCOND, null);
} else {
finallyMap.put(lastJump, null);
}
}
private void handleStmt(Node node) {
// Simply transfer to the next line.
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) {
return computeFollowNode(node, node, cfa);
}
static Node computeFollowNode(Node node) {
return computeFollowNode(node, node, null);
}
/**
* Computes the follow() node of a given node and its parent. There is a side
* effect when calling this function. If this function computed an edge that
* exists a FINALLY, it'll attempt to connect the fromNode to the outer
* FINALLY according to the finallyMap.
*
* @param fromNode The original source node since {@code node} is changed
* during recursion.
* @param node The node that follow() should compute.
*/
private static Node computeFollowNode(
Node fromNode, Node node, ControlFlowAnalysis cfa) {
/*
* This is the case where:
*
* 1. Parent is null implies that we are transferring control to the end of
* the script.
*
* 2. Parent is a function implies that we are transferring control back to
* the caller of the function.
*
* 3. If the node is a return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
*
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.isFunction() ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT_CASE:
// After the body of a CASE, the control goes to the body of the next
// case,
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (handler.isFunction()) {
return;
}
Preconditions.checkState(handler.isTry());
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
!cur.isScript() && !cur.isFunction();
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.isBlock() &&
block.getParent().isTry() &&
block.getParent().getFirstChild() == block) {
for (Node s = block.getNext(); s != null; s = s.getNext()) {
if (NodeUtil.hasCatchHandler(s)) {
return s.getFirstChild();
}
}
}
return null
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> templatized; extend the logic in this function when
* more types can be templatized.
* @param baseType the type to be templatized.
* @param templatizedTypes a list of the template JSTypes. Will be matched by
* list order to the template keys on the base type.
*/
public TemplatizedType createTemplatizedType(
ObjectType baseType, ImmutableList<JSType> templatizedTypes) {
// Only ObjectTypes can currently be templatized; extend this logic when
// more types can be templatized.
return new TemplatizedType(this, baseType, templatizedTypes);
}
/**
* Creates a templatized instance of the specified type. Only ObjectTypes
* can currently be templatized; extend the logic in this function when
* more types can be templatized.
* @param baseType the type to be templatized.
* @param templatizedTypes a list of the template JSTypes. Will be matched by
* list order to the template keys on the base type.
*/
public TemplatizedType createTemplatizedType(
ObjectType baseType, JSType... templatizedTypes) {
return createTemplatizedType(
baseType, ImmutableList.copyOf(templatizedTypes));
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName);
}
}
return createFromTypeNodesInternal(n, sourceName, scope);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope) */
private JSType createFrom
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>TypeNodesInternal(Node n, String sourceName,
StaticScope<JSType> scope) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(UNKNOWN_TYPE);
}
return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.ELLIPSIS: // Var args
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
int nAllowedTypes =
namedType.getTemplateTypeMap().numUnfilledTemplateKeys();
if (typeList != null && nAllowedTypes > 0) {
// Templatized types.
ImmutableList.Builder<JSType> templateTypes =
ImmutableList.builder();
// Special case for Object, where Object.<X> implies Object.<?,X>.
if (n.getString().equals("Object") &&
typeList.getFirstChild() == typeList.getLastChild()) {
templateTypes.add(getNativeType(UNKNOWN_TYPE));
}
int templateNodeIndex = 0;
for (Node templateNode : typeList.getFirstChild().siblings()) {
// Don't parse more templatized type nodes
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> /**
* Gets a comparator for the nodes. The default implementation returns
* {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}.
* @param isForward Whether the comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overridden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/**
* Exception-handling code paths.
* Conflates two kind of control flow passing:
* - An exception is thrown, and falls into a catch or finally block
* - During exception handling, a finally block finishes and control
* passes to the next finally block.
* In theory, we need 2 different edge types. In practice, we
* can just treat them as "the edges we can't really optimize".
*/
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that are also represented by other
* control flow graph nodes.
*
* <p>For example, traversing an IF node as root will visit the two subtrees
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edges.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// bleeds into the local scope and parameters are assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body, represented by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>, ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE);
declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE);
declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
declareNativeValueType(s, "undefined", VOID_TYPE);
// There is no longer a need to special case ActiveXObject
// but this remains here until we can get the extern forks
// cleaned up.
declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE);
return s;
}
private void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, NodeUtil.getBestJSDocInfo(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.isAssign()) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n);
break;
case Token.VAR:
defineVar(n);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
// Analyze any @lends object literals in this statement.
if (n.getParent() != null && NodeUtil.isStatement(n) &&
lentObjectLiterals != null) {
for (Node objLit : lentObjectLiterals) {
defineObjectLiteral(objLit);
}
lentObjectLiterals.clear();
}
}
private void attachLiteralTypes(Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> Functions assigned in conditional blocks are inferred.
for (Node current = n.getParent();
!(current.isScript() || current.isFunction());
current = current.getParent()) {
if (NodeUtil.isControlStructure(current)) {
return true;
}
}
// Check if this is assigned in an inner scope.
// Functions assigned in inner scopes are inferred.
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents == null ||
!contents.getEscapedQualifiedNames().contains(qName)) {
return false;
}
}
}
return inferred;
}
private boolean isConstantSymbol(JSDocInfo info, Node node) {
if (info != null && info.isConstant()) {
return true;
}
switch (node.getType()) {
case Token.NAME:
return NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), node, node.getParent());
case Token.GETPROP:
return node.isQualifiedName() && NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), node.getLastChild(), node);
}
return false;
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub declarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS> a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final JSType thisType;
CollectProperties(JSType thisType) {
this.thisType = thisType;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isExprResult()) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(child, child, null);
break;
}
}
}
private void maybeCollectMember(Node member,
Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
!member.isGetProp() ||
!member.getFirstChild().isThis()) {
return;
}
member.getFirstChild().setJSType(thisType);
// TODO(johnlenz): We are evaluating these values in the wrong scope:
// https://code.google.com/p/closure-compiler/issues/detail?id=926
JSType thisObjectType = thisType.toObjectType();
if (thisObjectType != null) {
ImmutableList<TemplateType> keys =
thisObjectType.getTemplateTypeMap().getTemplateKeys();
typeRegistry.setTemplateTypeNames(keys);
}
JSType jsType = getDeclaredType(info, member, value);
if (thisObjectType != null) {
typeRegistry.clearTemplateTypeNames();
}
Node name = member.getLastChild();
if (jsType != null &&
(name.isName() || name.isString()) &&
thisType.toObjectType() != null) {
thisType.toObjectType().defineDeclaredProperty(
name.getString(),
jsType,
member);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recursive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, unknownType);
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.isGetProp()) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're building.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents != null) {
for (String varName : contents.getEscapedVarNames()) {
Var v = scope.getVar(varName);
Preconditions.checkState(v.getScope() == scope);
v.markEscaped();
}
for (Multiset.Entry<String> entry :
contents.getAssignedNameCounts().entrySet()) {
Var v = scope.getVar(entry
Closure, 170
<FILEB>
<CHANGES>
if (n.isName() && n.getString().equals(varName)) {
<CHANGEE>
<CHANGES>
if (parent.isAssign() && (parent.getFirstChild() == n)
&& isAssignChain(parent, cfgNode)) {
<CHANGEE>
<CHANGES>
return;
} else {
<CHANGEE>
<CHANGES>
}
<CHANGEE>
<CHANGES>
}
private boolean isAssignChain(Node child, Node ancestor) {
for (Node n = child; n!= ancestor; n = n.getParent()) {
if (!n.isAssign()) {
return false;
}
}
return true;
<CHANGEE>
<FILEE>
<FILEB>
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUseCfgNode.
*/
private void getNumUseInUseCfgNode(final Node cfgNode) {
numUsesWithinCfgNode = 0;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
<CHANGES>
if (n.isName() && n.getString().equals(varName) &&
<CHANGEE>
// We make a special exception when the entire cfgNode is a chain
// of assignments, since in that case the assignment statements
// will happen after the inlining of the right hand side.
// TODO(blickly): Make the SIDE_EFFECT_PREDICATE check more exact
// and remove this special case.
<CHANGES>
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
<CHANGEE>
// Don't count lhs of top-level assignment chain
<CHANGES>
<CHANGEE>
numUsesWithinCfgNode++;
<CHANGES>
<CHANGEE>
}
<CHANGES>
<CHANGEE>
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
<FILEE>
<SCANS>.getElement());
Preconditions.checkState(v.getScope() == scope);
if (entry.getCount() == 1) {
v.markAssignedExactlyOnce();
}
}
}
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) {
return;
}
if (n.isParamList() && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
* Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node iifeArgumentNode = null;
if (NodeUtil.isCallOrNewTarget(functionNode)) {
iifeArgumentNode = functionNode.getNext();
}
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
JSType paramType = jsDocParameter == null ?
unknownType : jsDocParameter.getJSType();
boolean inferred = paramType == null || paramType == unknownType;
if (iifeArgumentNode != null && inferred) {
String argumentName = iifeArgumentNode.getQualifiedName();
Var argumentVar =
argumentName == null || scope.getParent() == null
? null : scope.getParent().getVar(argumentName);
if (argumentVar != null && !argumentVar.isTypeInferred()) {
paramType = argumentVar.getType();
}
}
if (paramType